Rust でモジュールを定義する (mod)

規模の大きい Rust プログラムを作る場合、モジュール の仕組みを使ってコードを階層化すると見通しがよくなります。 Rust でモジュールを定義するときは、mod キーワードを使用します。 1 つの .rs ファイルの中にインライン形式でモジュールを定義することも、別ファイルに分けてモジュールを定義することもできます。

クレートとモジュール

すべての Rust プログラムはクレート (crate) であり、バイナリクレートライブラリクレート のどちらかです。 クレートは、Rust におけるコンパイルの単位です。

  • バイナリクレート … 1 つの実行ファイルを作り上げるもの。
  • ライブラリクレート … 1 つのライブラリを作り上げるもの。

コンパイルはクレート単位で行われますが、コードレベルの実装では、モジュール という論理的な単位で分割/階層化できるようになっています。 クレートは 1 つ以上のモジュールで構成され、必ず 1 つの ルートモジュール(クレートルート) を持っています。 ルートモジュールのファイル名は決まっていて、バイナリクレートの場合は src/main.rs で、ライブラリクレートの場合は src/lib.rs です。 ルートモジュールはサブモジュールを含むことができ、さらに、サブモジュールも同様にサブモジュールを含むことができます。

クレートの論理的構造(ディレクトリ構造ではない)
ルートモジュール (src/main.rs あるいは src/lib.rs)
  +-- サブモジュール
  |     +-- サブサブモジュール
  |     +-- ...
  +-- サブモジュール
        +-- サブサブモジュール
        +-- ...

上記のように、クレート内のモジュールはルートモジュールを起点とするツリー構造になるため、mod キーワードを使ってモジュールを定義するとき、それは必ず何らかの親モジュールのサブモジュールということになります。

モジュールの作り方

インラインモジュール

mod ブロックを使ってサブモジュールを定義することができます。 サブモジュールの実装をブロック内に直接記述するので、インラインモジュール と呼ばれます。 次の例では、calc モジュールを定義して、その中に add 関数を定義しています。

src/main.rs
// calc モジュールを定義する
mod calc {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
}

fn main() {
    // calc モジュール内の add 関数を呼び出す
    println!("{}", calc::add(1, 2));
}

モジュール内で定義した関数は、デフォルトでモジュール内からのみ参照可能 (private) になっているため、別モジュールから add 関数を呼び出せるようにするには pub キーワードを付けて公開設定しておく必要があります(pub の詳細は後述)。 親モジュール(ここではルートモジュール)から calc モジュールの add 関数にアクセスするには、calc::add というパスを使用します。

ファイルモジュール

サブモジュールを別ファイルとして実装することもできます。 というより、大きなプロジェクトを管理する場合、こちらのファイルを使ったサブモジュール化が主な使い方になると思います。

例えば、ルートモジュール (main.rs) から呼び出すことのできる calc サブモジュールを作るには、main.rs と同じディレクトリに、calc.rs あるいは calc/mod.rs を配置します。 つまり、ディレクトリ構成は次のようになります。

calc モジュールの配置方法 (1) 2018 edition 以降
src/
  +-- main.rs
  +-- calc.rs
calc モジュールの配置方法 (2) 2015 edition 以降
src/
  +-- main.rs
  +-- calc/
        +-- mod.rs

別ファイルに分離した calc モジュールのコードは次のようになります。 先ほど mod calc ブロックの中に記述したコードと同じ内容です。

src/calc.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

これをルートモジュールから利用するには、次のように mod calc; というモジュール宣言を行うことで、src/calc.rs(あるいは src/calc/mod.rs)の内容を読み込むようコンパイラに指示します。 あとは、先ほどの例と同様、calc::add という形でモジュール内の関数を呼び出せます。

src/main.rs
mod calc;  // コンパイラに calc.rs(あるいは calc/mod.rs)が存在することを知らせる

fn main() {
    println!("{}", calc::add(1, 2));
}

サブサブモジュール

サブモジュール内でさらに mod キーワードを使えば、ネストする形でサブモジュールを定義することができます(以下、サブサブモジュール)。 ここでは例として、sub という名前のサブモジュールと、subsub という名前のサブサブモジュールを作ることにします。 サブサブモジュールは、サブモジュールと同じ名前のディレクトリ名 (sub) に .rs ファイルを配置することで作成できます。

サブサブモジュールの配置
src/
  +-- main.rs
  +-- sub.rs (サブモジュール)
  +-- sub/
       +-- subsub.rs (サブサブモジュール)

あるいは、mod.rs を使う場合は次のような配置になります。 src ディレクトリの一階層目をスッキリさせたいときは、こちらの配置の方がよいですね。

src/
  +-- main.rs
  +-- sub/
       +-- mod.rs (サブモジュール)
       +-- subsub.rs (サブサブモジュール)

以下、それぞれの .rs ファイルの実装例です。 pub キーワードで公開設定しなければいけないのは、サブサブモジュールでも同様です(ここでは文字列定数 MY_NAME を公開設定しています)。

main.rs(ルートモジュール)
mod sub;  // sub.rs の存在を知らせる

fn main() {
    sub::hello();  //=> "Hello, Maku"
}
sub.rs(サブモジュール)
mod subsub;  // sub/subsub.rs の存在を知らせる

pub fn hello() {
    println!("Hello, {}", subsub::MY_NAME);
}
sub/subsub.rs(サブサブモジュール)
pub const MY_NAME: &str = "Maku";

デフォルトでは、サブモジュールには、その親モジュールからしかアクセスできないようになっています。 上記の例で言うと、ルートモジュール (main.rs) からの sub モジュールの参照や、sub モジュールからの subsub モジュールの参照は可能ですが、ルートモジュールから直接 subsub モジュールを参照することはできません。 sub モジュールの外から subsub モジュールにアクセスできるようにするには、次のように pub mod を使ってモジュールを公開設定します。

sub.rs
pub mod subsub;

ここまで、pub キーワードを付けて公開設定してきたのは、モジュール内のメンバー(関数や定数)でしたが、ネストされたモジュールの公開設定も同様の仕組みで行えるということですね。 下記のサンプルコードでは、ルートモジュール (main.rs) から、サブサブモジュールに直接アクセスしています(sub::subsub という階層化されたパスを使用します)。

main.rs(ルートモジュール)
mod sub;

fn main() {
    println!("VALUE_1 = {}", sub::VALUE_1);  //=> 100
    println!("VALUE_2 = {}", sub::VALUE_2);  //=> 200
    println!("VALUE_3 = {}", sub::subsub::VALUE_3);  //=> 300
    println!("VALUE_4 = {}", sub::subsub::VALUE_4);  //=> 400
}
sub.rs(サブモジュール)
pub mod subsub;  // subsub モジュールを公開設定
pub const VALUE_1: i32 = 100;
pub const VALUE_2: i32 = 200;
sub/subsub.rs(サブサブモジュール)
pub const VALUE_3: i32 = 300;
pub const VALUE_4: i32 = 400;

ちなみに、sub::subsub::VALUE_3 と絶対パス指定している部分は、use を使って次のように省略記述 (subsub::VALUE_3) できるようになります。

main.rs
mod sub;
use sub::subsub;

fn main() {
    // ...
    println!("VALUE_3 = {}", subsub::VALUE_3);
}

公開範囲を制限する

ここまでの例では、シンプルな pub 指定による公開設定をしていましたが、これは、正確には 外部のクレートにまで公開する という意味になります。 外部クレートに公開するといっても、実際には、サブモジュールを pub 修飾しない限り、サブモジュール内のメンバーには外からはアクセスできないので、それだけで情報がダダ漏れになるということはありません。 とはいえ、公開範囲はできるだけ絞った方がよく、次のような追加のパス指定で、細かく公開範囲を制御できるようになっています。

指定方法意味
pub外部のクレートにまで公開
pub(crate)カレントクレートに公開
pub(super)親モジュールに公開 (おすすめ)
pub(super::super)親モジュールの親モジュールに公開
pub(in crate::my_mod)指定したパスのモジュールに公開(上位のモジュールのみを指定可能)
(指定なし)プライベート(モジュール内でのみ参照可能)
pub(self)同上(モジュール内でのみ参照可能)

上の指定方法の中でも使っていますが、モジュールパスは次のようなプレフィックスを組み合わせて指定することができます。

  • self:: … 自分自身のモジュールからの相対パス
  • super:: … 親モジュールからの相対パス
  • crate:: … クレートルートからの絶対パス

クレート内でのみ使用する要素には、pub(crate) 以上の公開範囲(つまり pub)を設定する必要はありません。 一般的に、モジュールの依存関係はツリー構造に従ったシンプルな構成になっていると保守しやすいので、まずは pub(super) で公開設定できないかを考えてみるとよいです。