モジュールを複数のファイルに分割する


102

それぞれ独自のファイルにある複数の構造体を含むモジュールが必要です。Math例としてモジュールを使用する:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

次のように、各構造体をメインファイルから使用する同じモジュールに配置します。

use Math::Vector;

fn main() {
  // ...
}

しかし、Rustのモジュールシステム(最初は少し混乱します)は、これを行う明確な方法を提供していません。モジュール全体を1つのファイルに入れることだけができるようです。これは素朴ですか?そうでない場合、どうすればよいですか?


1
「複数の構造体があり、それぞれが独自のファイルになっているモジュールが欲しい」と解釈しました。つまり、各構造体の定義を独自のファイルに入れたいということです。
BurntSushi5 14年

1
モジュールシステムは確かにそのような構造化を許可しますが、これは素朴であるとは見なされません。通常、モジュールパスがファイルシステムパスに直接対応していることが望ましいです。たとえば、struct foo::bar::Bazfoo/bar.rsまたはで定義する必要がありfoo/bar/mod.rsます。
Chris Morgan

回答:


111

Rustのモジュールシステムは実際には非常に柔軟であり、コードがファイルでどのように構造化されているかを隠しながら、どのような種類の構造でも公開できます。

ここで重要なのはpub use、他のモジュールから識別子を再エクスポートできるようにするを利用することだと思います。Rustのstd::ioクレートにはこれに先例があり、サブモジュールの一部のタイプがで使用するために再エクスポートされますstd::io

編集(2019-08-25):回答の次の部分はかなり前に書かれました。そのようなモジュール構造をrustc単独でセットアップする方法を説明します。今日、ほとんどのユースケースで通常Cargoを使用します。以下はまだ有効ですが、その一部(例#![crate_type = ...]:)は奇妙に見えるかもしれません。これは推奨されるソリューションではありません。

例を適応させるために、次のディレクトリ構造から始めることができます。

src/
  lib.rs
  vector.rs
main.rs

これがあなたmain.rsです:

extern crate math;

use math::vector;

fn main() {
    println!("{:?}", vector::VectorA::new());
    println!("{:?}", vector::VectorB::new());
}

そしてあなたsrc/lib.rs

#[crate_id = "math"];
#[crate_type = "lib"];

pub mod vector; // exports the module defined in vector.rs

そして最後にsrc/vector.rs

// exports identifiers from private sub-modules in the current
// module namespace
pub use self::vector_a::VectorA;
pub use self::vector_b::VectorB;

mod vector_b; // private sub-module defined in vector_b.rs

mod vector_a { // private sub-module defined in place
    #[derive(Debug)]
    pub struct VectorA {
        xs: Vec<i64>,
    }

    impl VectorA {
        pub fn new() -> VectorA {
            VectorA { xs: vec![] }
        }
    }
}

そして、これが魔法が起こる場所です。math::vector::vector_a特別な種類のベクトルを実装したサブモジュールを定義しました。ただし、ライブラリのクライアントにvector_aサブモジュールの存在を気にかけたくありません。代わりに、math::vectorモジュールで使用できるようにします。これは、現在のモジュールの識別子pub use self::vector_a::VectorAを再エクスポートするで行われvector_a::VectorAます。

しかし、特別なベクトル実装を別のファイルに入れることができるように、これを行う方法を尋ねました。これはmod vector_b;ラインが行うことです。Rustコンパイラにvector_b.rs、そのモジュールの実装用のファイルを探すように指示します。そして確かに、これが私たちのsrc/vector_b.rsファイルです:

#[derive(Debug)]
pub struct VectorB {
    xs: Vec<i64>,
}

impl VectorB {
    pub fn new() -> VectorB {
        VectorB { xs: vec![] }
    }
}

クライアントの観点からするVectorAと、およびVectorBが2つの異なるファイルの2つの異なるモジュールで定義されているという事実は完全に不透明です。

と同じディレクトリにいる場合は、次のコマンドでmain.rs実行できます。

rustc src/lib.rs
rustc -L . main.rs
./main

一般的に、Rustブックの「クレートとモジュール」の章はかなり良いです。多くの例があります。

最後に、Rustコンパイラは自動的にサブディレクトリも検索します。たとえば、上記のコードは次のディレクトリ構造で変更せずに機能します。

src/
  lib.rs
  vector/
      mod.rs
      vector_b.rs
main.rs

コンパイルして実行するコマンドも同じです。


私は「ベクター」の意味を誤解していると思います。私は、データ構造ではなく、数学的な量のようにベクトルについて話しいまし。また、Windowsでビルドするのは少し面倒なので、最新バージョンのrustを実行していません。
starscape 2014年

+1正確には私が必要としていたものではありませんでしたが、正しい方向に向けられました。
starscape 2014年

@EpicPineapple確かに!そして、Vecはそのようなベクトルを表すために使用できます。(もちろん、より大きなNの場合。)
BurntSushi5 2014年

1
@EpicPineapple私の答えを更新して、私がそれを更新できるように、何を逃したのか説明してくれませんか。のmath::Vec2代わりに使用する以外に、あなたの答えと私のものの違いを確認するのに苦労していますmath::vector::Vec2。(つまり、同じコンセプトですが、モジュールが1つ深くなっています。)
BurntSushi5 2014年

1
あなたの質問にはその基準は見当たらない。私が見る限り、私は尋ねられた質問に答えました。(それは本当にファイルからモジュールを離婚する方法を尋ねていました。)Rust 0.9で動作しないことを申し訳ありませんが、それは不安定な言語を使用する領域に付属しています。
BurntSushi5 2014年

38

Rustモジュールのルールは次のとおりです。

  1. ソースファイルは、それ自体のモジュールです(特殊ファイルmain.rs、lib.rs、およびmod.rsを除く)。
  2. ディレクトリは単なるモジュールパスコンポーネントです。
  3. mod.rsファイル、ディレクトリのモジュールにすぎません

ディレクトリmath内のファイルmatrix.rs 1は、単なるモジュールですmath::matrix。それは簡単です。ファイルシステムに表示されるものは、ソースコードにもあります。これは、ファイルパスとモジュールパスの1対1の対応です2

したがって、構造体はディレクトリmath内のファイルmatrix.rs内にあるため、で構造体Matrixをインポートできuse math::matrix::Matrixます。満足していない?use math::Matrix;代わりに非常に好むでしょうね。それが可能だ。math::matrix::Matrixmath / mod.rsの識別子を次のように再エクスポートします。

pub use self::math::Matrix;

これを機能させるには別のステップがあります。Rustはモジュールをロードするためにモジュール宣言を必要とします。mod math;main.rsにを追加します。そうしないと、次のようにインポートするときにコンパイラからエラーメッセージが表示されます。

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?

ヒントはここでは誤解を招くものです。もちろん、実際に別のライブラリを作成するつもりがある場合を除いて、追加のクレートは必要ありません。

これをmain.rsの先頭に追加します。

mod math;
pub use math::Matrix;

モジュール宣言はまた、サブモジュールのためにneccessaryでvectormatrixかつcomplexので、mathニーズが再輸出それらにそれらをロードします。識別子の再エクスポートは、識別子のモジュールをロードした場合にのみ機能します。これは、再輸出識別子にmath::matrix::Matrixあなたが書く必要がありますmod matrix;。あなたはmath / mod.rsでこれを行うことができます。したがって、次の内容のファイルを作成します。

mod vector;
pub use self::vector::Vector;

mod matrix;
pub use self::matrix::Matrix;

mod complex;
pub use self::complex::Complex;

ああ、あなたは終わった。


1ソースファイル名は通常、Rustでは小文字で始まります。そのため、Matrix.rsではなくmatrix.rsを使用します。

2 Javaは異なります。パスもで宣言しますpackage。それは冗長です。パスは、ファイルシステムのソースファイルの場所から既に明らかです。ファイルの上部にある宣言でこの情報を繰り返すのはなぜですか?もちろん、ファイルのファイルシステムの場所を見つけるよりも、ソースコードをざっと見た方が簡単な場合もあります。混乱が少ないと言う人も理解できます。


23

Rusts純粋主義者はおそらく私を異端者と呼んでこの解決策を嫌いますが、これははるかに簡単です。それぞれのファイルで独自の処理を行い、mod.rsで" include! "マクロを使用します。

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");

これにより、ネストされたモジュールが追加されることはなく、複雑なエクスポートと書き換えのルールを回避できます。シンプルで効果的、大騒ぎなし。


1
名前空間を捨てました。あるファイルを別のファイルと無関係な方法で変更すると、他のファイルが壊れる可能性があります。'use'の使用が漏洩します(つまり、すべてがのようになりますuse super::*)。他のファイルからコードを隠すことはできません(これは、安全に使用できない安全な抽象化にとって重要です)
Demur Rumed

11
うん、でもそれがまさに私がその場合に欲しかったものだ。名前空間化の目的で1つのファイルとして動作するファイルがいくつかある。すべてのケースでこれを推奨しているわけではありませんが、何らかの理由で「ファイルごとに1つのモジュール」メソッドを処理したくない場合は、これは便利な回避策です。
hasvn 2016

これはすばらしいです。モジュールの一部は内部のみですが自己完結型であり、これでうまくいきました。私も適切なモジュールソリューションが機能するように努力しますが、それほど簡単ではありません。
rjh 2017

5
私は異端者と呼ばれることを気にしません、あなたの解決策は便利です!
sailfish009

20

了解しましたpub use。しばらくコンパイラを試してみて、ようやく動作しました(指摘してくれたBurntSushiに感謝します)。

main.rs:

use math::Vec2;
mod math;

fn main() {
  let a = Vec2{x: 10.0, y: 10.0};
  let b = Vec2{x: 20.0, y: 20.0};
}

math / mod.rs:

pub use self::vector::Vec2;
mod vector;

math / vector.rs

use std::num::sqrt;

pub struct Vec2 {
  x: f64,
  y: f64
}

impl Vec2 {
  pub fn len(&self) -> f64 {
    sqrt(self.x * self.x + self.y * self.y) 
  }

  // other methods...
}

他の構造体も同じ方法で追加できます。注:マスターではなく0.9でコンパイルされています。


4
なお、の使用mod math;main.rsのカップルあなたのmainライブラリとプログラム。mathモジュールを独立させたい場合は、個別にコンパイルしてリンクする必要がありますextern crate math(私の回答に示されています)。Rust 0.9では、構文がextern mod math代わりになる可能性があります。
BurntSushi5 14年

20
BurntSushi5の答えを正しいものとしてマークすることは本当に公正でした。
IluTov 2015

2
@NSAddictいいえ。ファイルからモジュールを離婚するために、別のクレートを作成する必要はありません。過剰に設計されています。
nalply

1
なぜこれがトップ投票の答えにならないのですか?質問は、プロジェクトをいくつかのファイルに分割する方法を尋ねました。これは、この回答が示すように簡単です。それを木箱に分割する方法ではなく、困難であり、@ BurntSushi5が回答したものです(多分、質問は編集されましたか?)。 ..
レナート

6
@ BurntSushi5の回答が受け入れられたはずです。それは社会的に厄介であり、おそらく質問をして、非常に良い答えを得て、それを個別の回答として要約し、要約を承認済みの回答としてマークすることを意味することさえあります。
hasanyasin

3

Rustファイルが深くネストされている場合に、Rustファイルを含める方法をここに追加します。私は次の構造を持っています:

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

あなたはどのようにアクセスしますsink.rstoilet.rsからmain.rs

他の人が述べたように、Rustはファイルについての知識がありません。代わりに、すべてをモジュールおよびサブモジュールと見なします。バスルームディレクトリ内のファイルにアクセスするには、ファイルをエクスポートするか、上部にバレルする必要があります。これを行うには、アクセスしたいディレクトリとpub mod filename_inside_the_dir_without_rs_extファイル内のファイル名を指定します。

例。

// sink.rs
pub fn run() { 
    println!("Wash my hands for 20 secs!");
}

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. ディレクトリbathroom.rs内に呼び出されるファイルを作成しますhome

  2. ファイル名をエクスポートします。

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. home.rsnext というファイルを作成しますmain.rs

  4. pub mod bathroom.rsファイル

    // home.rs
    pub mod bathroom;
  5. 以内に main.rs

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    mod home;
    
    fn main() {
        home::bathroom::sink::run();
    }

    use ステートメントも使用できます。

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    use home::bathroom::{sink, toilet};
    
    fn main() {
        sink::run();
        sink::toilet();
    }

サブモジュール内に他の兄弟モジュール(ファイル)を含める

sink.rsfrom を使用toilet.rsする場合は、selfまたはsuperキーワードを指定してモジュールを呼び出すことができます。

// inside toilet.rs
use self::sink;
pub fn run() {
  sink::run();
  println!("Ahhh... This is sooo relaxing.")
}

最終的なディレクトリ構造

あなたはこのようなものになるでしょう:

|-----main.rs
|-----home.rs
|-----home/
|---------bathroom.rs
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

上記の構造は、Rust 2018以降でのみ機能します。次のディレクトリ構造は2018年にも有効ですが、2015年の仕組みです。

|-----main.rs
|-----home/
|---------mod.rs
|---------bathroom/
|-----------------mod.rs
|-----------------sink.rs
|-----------------toilet.rs

はとhome/mod.rs同じで、./home.rshome/bathroom/mod.rs同じhome/bathroom.rsです。ディレクトリと同じ名前のファイルを含めるとコンパイラが混乱するため、Rustはこの変更を行いました。2018バージョン(最初に表示されたバージョン)では、この構造が修正されています。

詳細についてはこのリポジトリを、全体的な説明についてはこのYouTubeビデオを参照してください。

最後に...ハイフンを避けてください!snake_case代わりに使用してください。

重要な注意点

最上位のファイルで深いファイルが必要ない場合でも、すべてのファイルを最上位にバレルする必要あります。

つまり、sink.rsを発見するには、toilet.rs上記の方法を使用してmain.rs!までバレルする必要があります。

言い換えれば、あなたがそれらを!まで露出していなければpub mod sink;use self::sink; 内部または内部toilet.rsは機能しませんmain.rs

したがって、常にファイルを先頭にバレルすることを忘れないでください!


2
...それは何かを言っているC ++と比較してめちゃくちゃ複雑です
ジョセフガービン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.