モジュールでスコープと公開範囲を制御する
この節では、モジュールと、モジュールシステムのそのほかの要素について説明します。具体的には、アイテムに名前を付けられるようにする パス、パスをスコープに導入する use キーワード、そしてアイテムを公開にする pub キーワードです。また、as キーワード、外部パッケージ、グロブ演算子についても説明します。
モジュールのチートシート
モジュールとパスの詳細に入る前に、ここでは、モジュール、パス、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 で、次の内容が含まれています。
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {plant:?}!");
}
この pub mod garden; 行は、src/garden.rs にあるコードを取り込むようコンパイラに伝えています。内容は次のとおりです。
pub mod vegetables;
ここで、pub mod vegetables; は src/garden/vegetables.rs のコードも取り込まれることを意味します。そのコードは次のとおりです。
#[derive(Debug)]
pub struct Asparagus {}
では、これらの規則の詳細に入り、実際にそれらがどう動くかを見ていきましょう!
モジュールで関連するコードをグループ化する
モジュール を使うと、可読性と再利用のしやすさのために、クレート内のコードを整理できます。モジュールを使うと、アイテムの 公開範囲 も制御できます。というのも、モジュール内のコードはデフォルトで非公開だからです。非公開アイテムは、外部から利用できない内部実装の詳細です。モジュールやその内部のアイテムを公開にすることも選べます。そうすると、それらが公開され、外部のコードから利用したり依存したりできるようになります。
例として、レストランの機能を提供するライブラリクレートを書いてみましょう。コードの構成に集中するため、関数シグネチャは定義しますが、その本体は空のままにしておきます。
レストラン業界では、レストランの一部はホール、別の一部はバックヤードと呼ばれます。ホール は客がいる場所です。そこには、案内係が客を席へ案内する場所、給仕係が注文や会計を受ける場所、バーテンダーが飲み物を作る場所が含まれます。バックヤード は、シェフや調理師が厨房で働き、皿洗い係が片付けをし、マネージャーが事務作業を行う場所です。
クレートをこのように構造化するには、その機能を入れ子のモジュールに整理できます。cargo new restaurant --lib を実行して、restaurant という名前の新しいライブラリを作成してください。次に、いくつかのモジュールと関数シグネチャを定義するために、リスト7-1のコードを 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() {}
}
}
モジュールは、mod キーワードの後ろにモジュール名(この場合は front_of_house)を続けることで定義します。モジュール本体はその後、波括弧の中に入ります。モジュールの中には、今回の hosting モジュールや serving モジュールのように、さらに別のモジュールを置けます。モジュールには、構造体、列挙型、定数、トレイト、そしてリスト7-1のように関数など、ほかのアイテムの定義を置くこともできます。
モジュールを使うことで、関連する定義をひとまとめにし、なぜそれらが関連しているのかを名前で表せます。このコードを使うプログラマは、すべての定義を読み通す必要はなく、グループに基づいてコードをたどれるため、自分に関係のある定義を見つけやすくなります。このコードに新しい機能を追加するプログラマも、プログラムを整理された状態に保つために、コードをどこに置けばよいかがわかります。
先ほど、src/main.rs と src/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
このツリーは、いくつかのモジュールが他のモジュールの内側に入れ子になっていることを示しています。たとえば、hosting は front_of_house の内側に入れ子になっています。また、このツリーは一部のモジュールが 兄弟 であることも示しています。つまり、同じモジュール内で定義されているということです。hosting と serving は、front_of_house 内で定義された兄弟です。モジュールAがモジュールBの内側に含まれている場合、モジュールAはモジュールBの 子 であり、モジュールBはモジュールAの 親 であると言います。モジュールツリー全体が、暗黙の crate という名前のモジュールの下を根としていることに注意してください。
このモジュールツリーは、コンピュータ上のファイルシステムのディレクトリツリーを思い出させるかもしれません。これはとても的確な比較です!ファイルシステム内のディレクトリと同じように、コードを整理するためにモジュールを使います。そして、ディレクトリ内のファイルと同じように、モジュールを見つける方法が必要です。