モジュールを定義して、スコープとプライバシーを制御する

この節では、モジュールと、その他のモジュールシステムの要素 ――すなわち、要素に名前をつけるための パス 、パスをスコープに持ち込むuseキーワード、要素を公開するpubキーワード―― について学びます。 また、asキーワード、外部パッケージ、glob演算子についても話します。

まずは、将来コードを整理するときの簡単なリファレンスとして、規則の一覧を示します。 その後で各規則を詳細に説明します。

モジュールのチートシート

以下に、モジュール、パス、useキーワード、そしてpubキーワードがコンパイラ内でどう機能するか、そして多くの開発者はどのようにコードを整理するかについての、クイックリファレンスを提供します。 この章ではこれらの各規則の実例を見ていきますが、ここはモジュールがどう機能するか思い出すために見直すのに良い場所となるでしょう。

  • クレートルートから始める: クレートをコンパイルするとき、 コンパイラはまずコンパイル対象のコードとしてクレートルートファイル(通常は、ライブラリクレートでは src/lib.rs、バイナリクレートでは src/main.rs)の中を探します。
  • モジュールを宣言する: クレートルートファイルの中で、新しいモジュールを宣言することができます; 例えば、mod garden;として“garden”モジュールを宣言したとします。コンパイラは以下の場所からモジュールのコードを探します:
    • インライン。mod gardenの後のセミコロンが波かっこで置き換えられているとき、その中
    • src/garden.rs ファイルの中
    • src/garden/mod.rs ファイルの中
  • サブモジュールを宣言する: クレートルート以外のすべてのファイルの中で、サブモジュールを宣言することができます。 例えば、src/garden.rs 内でmod vegetables;と宣言することができます。コンパイラはサブモジュールのコードを、親モジュールに対応するディレクトリ内の以下の場所から探します:
    • インライン。mod vegetablesのすぐ後にセミコロンの代わりに波かっこがあるとき、その中
    • src/garden/vegetables.rs ファイルの中
    • src/garden/vegetables/mod.rs ファイルの中
  • モジュール内のコードへのパス: モジュールがクレートの一部となったら、プライバシー規則が許す限り同じクレート内のどこからでも、そのモジュール内のコードをコードへのパスを使用して参照できます。 例えば、garden vegetablesモジュール内のAsparagus型はcrate::garden::vegetables::Asparagusで参照できます。
  • 非公開と公開: モジュール内のコードはデフォルトでは非公開で、親モジュールからアクセスすることができません。 モジュールを公開にするには、modではなくpub modで宣言してください。 公開モジュール内の要素も公開にするには、それらの宣言の前にpubを付けてください。
  • useキーワード: useキーワードは、長いパスの繰り返しを減らすために、要素へのショートカットをスコープ内に作成します。 crate::garden::vegetables::Asparagusを参照できるスコープ内であれば、use crate::garden::vegetables::Asparagus;でショートカットを作成でき、 以降はそのスコープ内ではその型を使用するためにはAsparagusとだけ書けばよくなります。

これらの規則を説明するための例として、ここにbackyardという名前のバイナリクレートを作成します。 クレートのディレクトリは、同じくbackyardと名付けられ、以下のファイルとディレクトリを含んでいます:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

この場合のクレートルートファイルは src/main.rs であり、以下の内容を含んでいます:

ファイル名: src/main.rs

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {:?}!", plant);
}

pub mod garden;の行は、コンパイラにsrc/garden.rsで見つかるコードを含めるように指示します。 その内容は:

ファイル名: src/garden.rs

pub mod vegetables;

このpub mod vegetables;src/garden/vegetables.rs のコードも含めると言う意味です。 そのコードは:

#[derive(Debug)]
pub struct Asparagus {}

それでは、これらのルールの詳細を知り、実際に試してみましょう!

関連するコードをモジュールにまとめる

モジュール (module) はクレート内のコードを整理し、可読性と再利用性を上げるのに役に立ちます。 モジュール内のコードはデフォルトでは非公開なので、モジュールは要素のプライバシー (priavacy) の制御も可能にします。 非公開の要素は、外部からの使用ができない内部的な実装の詳細です。 モジュールとそれらの中の要素は公開にするかどうかを選ぶことができ、公開にすると、外部のコードがそれを使用し、それに依存できるようになります。

例えば、レストランの機能を提供するライブラリクレートを書いてみましょう。 レストランの実装ではなくコードの関係性に注目したいので、関数にシグネチャをつけますが中身は空白のままにします。

レストラン業界では、レストランの一部を接客部門 (front of house) といい、その他を後方部門 (back of house) といいます。 接客部門とはお客さんがいるところです。接客係がお客様を席に案内し、給仕係が注文と支払いを受け付け、バーテンダーが飲み物を作ります。 後方部門とはシェフや料理人がキッチンで働き、皿洗い係が食器を片付け、マネージャが管理業務をする場所です。

私達のクレートをこのような構造にするために、関数をネストしたモジュールにまとめましょう。 restaurantという名前の新しいライブラリをcargo new --lib restaurantと実行することで作成し、リスト7-1のコードを src/lib.rs に書き込み、モジュールと関数のシグネチャを定義してください。 以下が接客部門です:

ファイル名: src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}

リスト7-1: front_of_houseモジュールにその他のモジュールが含まれ、さらにそれらが関数を含んでいる

モジュールは、modキーワードと、それに続くモジュールの名前(今回の場合、front_of_house)によって定義されます。 次にモジュールの本体が波かっこの中に入ります。 モジュールの中には、今回だとhostingservingのように、他のモジュールをおくこともできます。 モジュールにはその他の要素の定義も置くことができます。例えば、構造体、enum、定数、トレイト、そして(リスト7-1のように)関数です。

モジュールを使うことで、関連する定義を一つにまとめ、関連する理由を名前で示せます。 このコードを使うプログラマーは、定義を全部読むことなく、グループ単位でコードを読み進められるので、欲しい定義を見つけ出すのが簡単になるでしょう。 このコードに新しい機能を付け加えるプログラマーは、プログラムのまとまりを保つために、どこにその機能のコードを置けば良いのかがわかるでしょう。

以前、 src/main.rssrc/lib.rs はクレートルートと呼ばれていると言いました。 この名前のわけは、 モジュールツリー と呼ばれるクレートのモジュール構造の根っこ (ルート)にこれら2つのファイルの中身がcrateというモジュールを形成するからです。

リスト7-2は、リスト7-1の構造のモジュールツリーを示しています。

crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

リスト7-2: リスト7-1 のコードのモジュールツリー

このツリーを見ると、どのモジュールがどのモジュールの中にネストしているのかがわかります; 例えば、hostingfront_of_houseの中にネストしています。 また、いくつかのモジュールはお互いに兄弟 (siblings) の関係にある、つまり、同じモジュール内で定義されていることもわかります; 例えばhostingservingfront_of_houseで定義されている兄弟同士です。 他にも、モジュールAがモジュールBの中に入っている時、AはBの子 (child) であるといい、BはAの親 (parent) であるといいます。 モジュールツリー全体が、暗黙のうちに作られたcrateというモジュールの下にあることにも注目してください。

モジュールツリーを見ていると、コンピュータのファイルシステムのディレクトリツリーを思い出すかもしれません。その喩えはとても適切です! ファイルシステムのディレクトリのように、モジュールはコードをまとめるのに使われるのです。 そしてディレクトリからファイルを見つけるように、目的のモジュールを見つけ出す方法が必要になります。