Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

モジュールでスコープと公開範囲を制御する

この節では、モジュールと、モジュールシステムのそのほかの要素について説明します。具体的には、アイテムに名前を付けられるようにする パス、パスをスコープに導入する 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.rsmod 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.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

このツリーは、いくつかのモジュールが他のモジュールの内側に入れ子になっていることを示しています。たとえば、hostingfront_of_house の内側に入れ子になっています。また、このツリーは一部のモジュールが 兄弟 であることも示しています。つまり、同じモジュール内で定義されているということです。hostingserving は、front_of_house 内で定義された兄弟です。モジュールAがモジュールBの内側に含まれている場合、モジュールAはモジュールBの であり、モジュールBはモジュールAの であると言います。モジュールツリー全体が、暗黙の crate という名前のモジュールの下を根としていることに注意してください。

このモジュールツリーは、コンピュータ上のファイルシステムのディレクトリツリーを思い出させるかもしれません。これはとても的確な比較です!ファイルシステム内のディレクトリと同じように、コードを整理するためにモジュールを使います。そして、ディレクトリ内のファイルと同じように、モジュールを見つける方法が必要です。