Rust のモジュールツリー内のパス

Beginner

This tutorial is from open-source community. Access the source code

はじめに

モジュールツリー内のアイテムを参照するためのパスへようこそ。この実験は、Rust Book の一部です。LabEx で Rust のスキルを練習することができます。

この実験では、Rust ではパスがモジュールツリー内のアイテムを参照するために使用され、絶対パスまたは相対パスの形式をとることができることを学びます。

モジュールツリー内のアイテムを参照するためのパス

Rust にモジュールツリー内のアイテムがどこにあるかを示すには、ファイルシステムをナビゲートする際にパスを使用するのと同じ方法でパスを使用します。関数を呼び出すには、そのパスを知る必要があります。

パスは 2 つの形式をとることができます。

  • 絶対パスは、クレートのルートから始まる完全なパスです。外部クレートのコードの場合、絶対パスはクレート名から始まり、現在のクレートのコードの場合、リテラル crate から始まります。
  • 相対パスは、現在のモジュールから始まり、selfsuper、または現在のモジュール内の識別子を使用します。

絶対パスと相対パスの両方の後には、2 つのコロン (::) で区切られた 1 つ以上の識別子が続きます。

リスト 7-1 に戻りますが、add_to_waitlist 関数を呼び出したいとしましょう。これは、「add_to_waitlist 関数のパスは何か?」と尋ねるのと同じことです。リスト 7-3 には、いくつかのモジュールと関数が削除されたリスト 7-1 が含まれています。

クレートのルートで定義された新しい関数 eat_at_restaurant から add_to_waitlist 関数を呼び出す 2 つの方法を示します。これらのパスは正しいですが、この例をそのままコンパイルできない別の問題が残っています。少し後でその理由を説明します。

eat_at_restaurant 関数は、私たちのライブラリクレートのパブリック API の一部であるため、pub キーワードでマークされています。「pub キーワードを使用したパスの公開」では、pub に関する詳細を説明します。

ファイル名:src/lib.rs

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

pub fn eat_at_restaurant() {
    // 絶対パス
    crate::front_of_house::hosting::add_to_waitlist();

    // 相対パス
    front_of_house::hosting::add_to_waitlist();
}

リスト 7-3: 絶対パスと相対パスを使用した add_to_waitlist 関数の呼び出し

eat_at_restaurantadd_to_waitlist 関数を最初に呼び出すとき、絶対パスを使用します。add_to_waitlist 関数は、eat_at_restaurant と同じクレートに定義されているため、絶対パスを始めるために crate キーワードを使用できます。その後、次々と続くモジュールを含め、add_to_waitlist に到達するまで進みます。同じ構造のファイルシステムを想像してみてください。add_to_waitlist プログラムを実行するには、パス /front_of_house/hosting/add_to_waitlist を指定します。クレートのルートから始めるためにクレート名を使用するのは、シェルでファイルシステムのルートから始めるために / を使用するのと同じです。

2 回目に eat_at_restaurantadd_to_waitlist を呼び出すとき、相対パスを使用します。このパスは、eat_at_restaurant と同じモジュールツリーのレベルで定義されたモジュールの名前である front_of_house から始まります。ここでのファイルシステムと同等のものは、パス front_of_house/hosting/add_to_waitlist を使用することになります。モジュール名から始まることは、パスが相対的であることを意味します。

相対パスと絶対パスをどちらを使用するかを選ぶかは、プロジェクトに応じて決定します。これは、アイテム定義コードを使用するコードとは別に、または一緒に移動する可能性が高いかどうかに依存します。たとえば、front_of_house モジュールと eat_at_restaurant 関数を customer_experience という名前のモジュールに移動した場合、add_to_waitlist への絶対パスを更新する必要がありますが、相対パスは依然として有効です。ただし、eat_at_restaurant 関数を dining という名前の別のモジュールに個別に移動した場合、add_to_waitlist 呼び出しの絶対パスは同じままですが、相対パスを更新する必要があります。一般的に私たちの好みは、コード定義とアイテム呼び出しを互いに独立して移動したい可能性が高いため、絶対パスを指定することです。

リスト 7-3 をコンパイルして、まだコンパイルできない理由を調べてみましょう!得られたエラーはリスト 7-4 に示されています。

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
 --> src/lib.rs:9:28
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                            ^^^^^^^ private module
  |
note: the module `hosting` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hosting {
  |     ^^^^^^^^^^^

error[E0603]: module `hosting` is private
  --> src/lib.rs:12:21
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                     ^^^^^^^ private module
   |
note: the module `hosting` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod hosting {
   |     ^^^^^^^^^^^

リスト 7-4: リスト 7-3 のコードをビルドしたときのコンパイラエラー

エラーメッセージは、モジュール hosting が非公開であることを示しています。言い換えると、hosting モジュールと add_to_waitlist 関数のパスは正しいですが、Rust は非公開セクションにアクセスできないため、それらを使用できません。Rust では、既定ですべてのアイテム(関数、メソッド、構造体、列挙体、モジュール、定数)は親モジュールに対して非公開です。関数や構造体などのアイテムを非公開にする場合は、モジュールに入れます。

親モジュール内のアイテムは、子モジュール内の非公開アイテムを使用できませんが、子モジュール内のアイテムは、その祖先モジュール内のアイテムを使用できます。これは、子モジュールがその実装詳細をラップして隠すためですが、子モジュールはそれが定義されているコンテキストを見ることができます。この比喩を続けると、プライバシールールはレストランのオフィスのようなものであると考えてください。そこで行われることは、レストランのお客にとって非公開ですが、オフィスマネージャーは、彼らが運営するレストランのすべてを見て、何でもできます。

Rust は、内部の実装詳細を隠すことが既定になるようにモジュールシステムを機能させるように選びました。このようにすることで、内部コードのどの部分を変更しても外部コードを破壊しないことがわかります。ただし、Rust は、pub キーワードを使用してアイテムを公開することで、子モジュールのコードの内部部分を外部の祖先モジュールに公開するオプションも提供しています。

pub キーワードを使用したパスの公開

リスト 7-4 のエラーに戻りましょう。そのエラーは、hosting モジュールが非公開であることを伝えていました。親モジュール内の eat_at_restaurant 関数が子モジュール内の add_to_waitlist 関数にアクセスできるようにするため、hosting モジュールを pub キーワードでマークします。これは、リスト 7-5 に示すようになります。

ファイル名:src/lib.rs

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

--snip--

リスト 7-5: eat_at_restaurant から使用するために hosting モジュールを pub として宣言する

残念ながら、リスト 7-5 のコードは依然としてコンパイラエラーを引き起こします。これは、リスト 7-6 に示すようになります。

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
 --> src/lib.rs:9:37
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `add_to_waitlist` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn add_to_waitlist() {}
  |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:12:30
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `add_to_waitlist` is defined here
  --> src/lib.rs:3:9
   |
3  |         fn add_to_waitlist() {}
   |         ^^^^^^^^^^^^^^^^^^^^

リスト 7-6: リスト 7-5 のコードをビルドしたときのコンパイラエラー

何が起こったのでしょうか? mod hosting の前に pub キーワードを追加することで、モジュールが公開されます。この変更により、front_of_house にアクセスできる場合、hosting にアクセスできるようになります。しかし、hosting内容 は依然として非公開です。モジュールを公開しても、その内容は公開されません。モジュールの pub キーワードは、その祖先モジュール内のコードがそれを参照できるようにするだけで、内部コードにアクセスすることはできません。モジュールはコンテナであるため、モジュールを公開するだけでは何もできません。さらに進んで、モジュール内の 1 つ以上のアイテムも公開するように選ぶ必要があります。

リスト 7-6 のエラーは、add_to_waitlist 関数が非公開であることを示しています。プライバシールールは、構造体、列挙体、関数、メソッドだけでなく、モジュールにも適用されます。

add_to_waitlist 関数の定義の前に pub キーワードを追加することで、add_to_waitlist 関数も公開しましょう。これは、リスト 7-7 に示すようになります。

ファイル名:src/lib.rs

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

--snip--

リスト 7-7: mod hostingfn add_to_waitlistpub キーワードを追加することで、eat_at_restaurant から関数を呼び出せるようになります。

今ではコードがコンパイルされます! pub キーワードを追加することで、これらのパスをプライバシールールに関して add_to_waitlist で使用できるようになる理由を見てみましょう。絶対パスと相対パスを見てみましょう。

絶対パスでは、クレートのモジュールツリーのルートである crate から始まります。front_of_house モジュールはクレートのルートに定義されています。front_of_house は公開されていませんが、eat_at_restaurant 関数は front_of_house と同じモジュールに定義されているため(つまり、eat_at_restaurantfront_of_house は兄弟です)、eat_at_restaurant から front_of_house を参照できます。次は、pub でマークされた hosting モジュールです。hosting の親モジュールにアクセスできるため、hosting にアクセスできます。最後に、add_to_waitlist 関数は pub でマークされており、その親モジュールにアクセスできるため、この関数呼び出しは機能します!

相対パスでは、最初のステップを除いては絶対パスと同じ論理になります。クレートのルートから始まるのではなく、パスは front_of_house から始まります。front_of_house モジュールは eat_at_restaurant と同じモジュール内に定義されているため、eat_at_restaurant が定義されているモジュールから始まる相対パスが機能します。その後、hostingadd_to_waitlistpub でマークされているため、パスの残りの部分が機能し、この関数呼び出しは有効です!

ライブラリクレートを共有して他のプロジェクトがコードを使用できるようにする予定がある場合、公開 API は、他の人がコードとどのようにやり取りできるかを決定する、クレートのユーザーとの契約になります。公開 API の変更を管理して、人々がクレートに依存しやすくするためには、多くの考慮事項があります。これらの考慮事項は、この本の範囲外です。このトピックに興味がある場合は、https://rust-lang.github.io/api-guidelines の Rust API ガイドラインを参照してください。

バイナリとライブラリを含むパッケージのベストプラクティス

パッケージには、src/main.rs のバイナリクレートルートと src/lib.rs のライブラリクレートルートの両方が含まれている場合があり、デフォルトでは両方のクレートにはパッケージ名が付きます。通常、ライブラリとバイナリクレートの両方を含むこのパターンのパッケージは、バイナリクレートには、ライブラリクレートのコードを呼び出す実行可能ファイルを起動するのに十分なコードがあります。これにより、他のプロジェクトがパッケージが提供する最も多くの機能の恩恵を受けることができます。なぜなら、ライブラリクレートのコードを共有できるからです。

モジュールツリーは src/lib.rs に定義する必要があります。その後、バイナリクレートでは、パッケージ名から始まるパスを使用して、任意の公開アイテムを使用できます。バイナリクレートは、完全に外部のクレートがライブラリクレートを使用するのと同じように、ライブラリクレートのユーザーになります。それは、公開 API のみを使用できます。これにより、良い API を設計するのに役立ちます。自分が作者であるだけでなく、クライアントでもあるからです!

第 12 章では、バイナリクレートとライブラリクレートの両方を含むコマンドラインプログラムを使って、この組織的な慣行を示します。

super で始まる相対パスの作成

パスの先頭に super を使用することで、現在のモジュールまたはクレートのルートではなく、親モジュールから始まる相対パスを構築することができます。これは、ファイルシステムパスを .. 構文で始めるのと同じです。super を使用することで、親モジュールにあると知っているアイテムを参照できるようになります。これにより、モジュールが親と密接に関連している場合でも、親がいつかモジュールツリーの他の場所に移動する可能性があるため、モジュールツリーを再配置する際に作業が容易になります。

シェフが注文の誤りを修正し、直接お客様に届ける状況を表現するコードをリスト 7-8 に示します。back_of_house モジュールに定義された fix_incorrect_order 関数は、super から始まる deliver_order へのパスを指定することで、親モジュールに定義された deliver_order 関数を呼び出します。

ファイル名:src/lib.rs

fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}

リスト 7-8: super から始まる相対パスを使用した関数の呼び出し

fix_incorrect_order 関数は back_of_house モジュールにあるため、super を使用して back_of_house の親モジュール(この場合はルートである crate)に移動することができます。そこから、deliver_order を探して見つけます。成功です!私たちは、back_of_house モジュールと deliver_order 関数がおそらく互いに同じ関係を維持し、クレートのモジュールツリーを再編成する場合に一緒に移動すると考えています。したがって、このコードが別のモジュールに移動した場合に将来コードを更新する場所が少なくなるように、super を使用しました。

構造体と列挙体の公開

構造体と列挙体を公開として指定するためにも pub を使用できますが、構造体と列挙体に対する pub の使用にはいくつかの追加の詳細があります。構造体定義の前に pub を使用すると、構造体が公開されますが、構造体のフィールドは依然として非公開になります。各フィールドを個別に公開するかどうかは、ケースバイケースで決定することができます。リスト 7-9 では、公開された toast フィールドと非公開の seasonal_fruit フィールドを持つ公開 back_of_house::Breakfast 構造体を定義しています。これは、レストランのケースをモデル化しており、お客様は食事に付くパンの種類を選ぶことができますが、シェフが季節に応じて在庫のある果物を選んで食事に添えます。利用可能な果物は急速に変化するため、お客様は果物を選ぶことも、また、どの果物が届くかを見ることもできません。

ファイル名:src/lib.rs

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    // 夏にライ麦トーストの朝食を注文する
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // 食べたいパンの種類を変更する
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // 次の行をコメントアウトを解除するとコンパイルエラーになります。食事に添えられる季節限定の果物を見たり変更したりすることはできません
    // meal.seasonal_fruit = String::from("blueberries");
}

リスト 7-9: 一部の公開フィールドと一部の非公開フィールドを持つ構造体

back_of_house::Breakfast 構造体の toast フィールドが公開されているため、eat_at_restaurant ではドット表記を使用して toast フィールドに書き込み、読み取りすることができます。seasonal_fruit フィールドは非公開であるため、eat_at_restaurant では使用できないことに注意してください。seasonal_fruit フィールドの値を変更する行のコメントアウトを解除して、どのエラーが表示されるか確認してみてください!

また、back_of_house::Breakfast が非公開フィールドを持っているため、構造体には Breakfast のインスタンスを構築する公開関連関数(ここでは summer と名付けました)が必要です。Breakfast にそのような関数がない場合、eat_at_restaurantBreakfast のインスタンスを作成できないことになります。なぜなら、eat_at_restaurant で非公開の seasonal_fruit フィールドの値を設定できないからです。

対照的に、列挙体を公開すると、そのすべてのバリアントが公開されます。enum キーワードの前に pub だけが必要です。これは、リスト 7-10 に示すようになります。

ファイル名:src/lib.rs

mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

リスト 7-10: 列挙体を公開として指定すると、そのすべてのバリアントが公開されます。

Appetizer 列挙体を公開として指定したため、eat_at_restaurant では SoupSalad のバリアントを使用できます。

列挙体のバリアントが公開されない限り、列挙体はあまり役に立ちません。すべてのケースで列挙体のバリアントすべてに pub を付けるのは面倒です。したがって、列挙体のバリアントの既定値は公開になっています。構造体は、フィールドが公開されていなくてもよく使われるため、構造体のフィールドは、pub で注釈付けされていない限り、既定ですべてが非公開であるという一般的なルールに従います。

pub に関するもう 1 つの状況がありますが、ここでは扱っていません。それは、最後のモジュールシステム機能である use キーワードです。まずは use 自体を扱い、その後、pubuse を組み合わせる方法を示します。

まとめ

おめでとうございます!モジュールツリー内のアイテムを参照するためのパスの実験を完了しました。LabEx でさらに多くの実験を行って、スキルを向上させることができます。