モジュールツリーの要素を示すためのパス

ファイルシステムの中を移動する時と同じように、Rustにモジュールツリー内の要素を見つけるためにはどこを探せばいいのか教えるためにパスを使います。 関数を呼び出したいなら、そのパスを知っていなければなりません。

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

  • 絶対パス は、クレートの名前かcrateという文字列を使うことで、クレートルートからスタートします。
  • 相対パス は、selfsuperまたは今のモジュール内の識別子を使うことで、現在のモジュールからスタートします。

絶対パスも相対パスも、その後に一つ以上の識別子がダブルコロン(::)で仕切られて続きます。

Listing 7-1の例に戻ってみましょう。 add_to_waitlist関数をどうやって呼べばいいでしょうか? すなわち、add_to_waitlistのパスは何でしょうか? Listing 7-3 は、モジュールと関数をいくつか取り除いてコードをやや簡潔にしています。 これを使って、クレートルートに定義された新しいeat_at_restaurantという関数から、add_to_waitlist関数を呼びだす2つの方法を示しましょう。 eat_at_restaurant関数はこのライブラリクレートの公開 (public) APIの1つなので、pubキーワードをつけておきます。 pubについては、パスをpubキーワードで公開するの節でより詳しく学びます。 この例はまだコンパイルできないことに注意してください。理由はすぐに説明します。

ファイル名: src/lib.rs

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

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

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

Listing 7-3: add_to_waitlist 関数を絶対パスと相対パスで呼び出す

eat_at_restaurantで最初にadd_to_waitlist関数を呼び出す時、絶対パスを使っています。 add_to_waitlist関数はeat_at_restaurantと同じクレートで定義されているので、crateキーワードで絶対パスを始めることができます。

crateの後は、add_to_waitlistにたどり着くまで、後に続くモジュールを書き込んでいます。 同じ構造のファイルシステムを想像すれば、/front_of_house/hosting/add_to_waitlistとパスを指定してadd_to_waitlistを実行していることに相当します。 crateという名前を使ってクレートルートからスタートするというのは、/を使ってファイルシステムのルートからスタートするようなものです。

eat_at_restaurantで2回目にadd_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への絶対パスは同じままですが、相対パスは更新しないといけないでしょう。 コードの定義と、その要素の呼び出しは独立に動かしそうなので、絶対パスのほうが好ましいです。

では、Listing 7-3 をコンパイルしてみて、どうしてこれはまだコンパイルできないのか考えてみましょう! エラーをListing 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();
  |                            ^^^^^^^

error[E0603]: module `hosting` is private
  --> src/lib.rs:12:21
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                     ^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`.

To learn more, run the command again with --verbose.

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

エラーメッセージは、hostingは非公開 (private) だ、と言っています。 言い換えるなら、hostingモジュールとadd_to_waitlist関数へのパスは正しいが、非公開な部分へのアクセスは許可されていないので、Rustがそれを使わせてくれないということです。

モジュールはコードの整理に役立つだけではありません。 モジュールはRustの プライバシー境界 も定義します。これは、外部のコードが知ったり、呼び出したり、依存したりしてはいけない実装の詳細をカプセル化する線引きです。 なので、関数や構造体といった要素を非公開にしたければ、モジュールに入れればよいのです。

Rustにおけるプライバシーは、「あらゆる要素(関数、メソッド、構造体、enum、モジュールおよび定数)は標準では非公開」というやり方で動いています。 親モジュールの要素は子モジュールの非公開要素を使えませんが、子モジュールの要素はその祖先モジュールの要素を使えます。 これは、子モジュールは実装の詳細を覆い隠しますが、子モジュールは自分の定義された文脈を見ることができるためです。 レストランの喩えを続けるなら、レストランの後方部門になったつもりでプライバシーのルールを考えてみてください。レストランの顧客にはそこで何が起こっているのかは非公開ですが、そこで働くオフィスマネージャには、レストランのことは何でも見えるし何でもできるのです。

Rustは、内部実装の詳細を隠すことが標準であるようにモジュールシステムを機能させることを選択しました。 こうすることで、内部のコードのどの部分が、外部のコードを壊すことなく変更できるのかを知ることができます。 しかし、pubキーワードを使って要素を公開することで、子モジュールの内部部品を外部の祖先モジュールに見せることができます。

パスをpubキーワードで公開する

Listing 7-4の、hostingモジュールが非公開だと言ってきていたエラーに戻りましょう。 親モジュールのeat_at_restaurant関数が子モジュールのadd_to_waitlist関数にアクセスできるようにしたいので、hostingモジュールにpubキーワードをつけます。Listing 7-5のようになります。

ファイル名: src/lib.rs

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

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

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

Listing 7-5: hosting モジュールを pub として宣言することでeat_at_restaurantから使う

残念ながら、Listing 7-5 のコードもListing 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();
  |                                     ^^^^^^^^^^^^^^^

error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:12:30
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                              ^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant`.

To learn more, run the command again with --verbose.

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

何が起きたのでしょう?pubキーワードをmod hostingの前に追加したことで、このモジュールは公開されました。 この変更によって、front_of_houseにアクセスできるなら、hostingにもアクセスできるようになりました。 しかしhosting中身 はまだ非公開です。モジュールを公開してもその中身は公開されないのです。 モジュールにpubキーワードがついていても、祖先モジュールのコードはモジュールを参照できるようになるだけです。

Listing 7-6 のエラーはadd_to_waitlist関数が非公開だと言っています。 プライバシーのルールは、モジュール同様、構造体、enum、関数、メソッドにも適用されるのです。

add_to_waitlistの定義の前にpubキーワードを追加して、これも公開しましょう。

ファイル名: src/lib.rs

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

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

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

fn main() {}

Listing 7-7: pubキーワードをmod hostingfn add_to_waitlistに追加することで、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が付いていますから、残りのパスについても問題はなく、この関数呼び出しは有効というわけです。

相対パスをsuperで始める

親モジュールから始まる相対パスなら、superを最初につけることで構成できます。 ファイルシステムパスを..構文で始めるのに似ています。 どのようなときのこの機能が使いたくなるのでしょう?

シェフが間違った注文を修正し、自分でお客さんに持っていくという状況をモデル化している、Listing 7-8 を考えてみてください。 fix_incorrect_order関数はserve_order関数を呼び出すために、superから始まるserve_order関数へのパスを使っています。

ファイル名: src/lib.rs

fn serve_order() {}

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

    fn cook_order() {}
}

fn main() {}

Listing 7-8: super で始まる相対パスを使って関数を呼び出す

fix_incorrect_order関数はback_of_houseモジュールの中にあるので、superを使ってback_of_houseの親モジュールにいけます。親モジュールは、今回の場合ルートであるcrateです。 そこから、serve_orderを探し、見つけ出します。 成功! もしクレートのモジュールツリーを再編成することにした場合でも、back_of_houseモジュールとserve_order関数は同じ関係性で有り続け、一緒に動くように思われます。 そのため、superを使うことで、将来このコードが別のモジュールに移動するとしても、更新する場所が少なくて済むようにしました。

構造体とenumを公開する

構造体やenumもpubを使って公開するよう指定できますが、追加の細目がいくつかあります。 構造体定義の前にpubを使うと、構造体は公開されますが、構造体のフィールドは非公開のままなのです。 それぞれのフィールドを公開するか否かを個々に決められます。 Listing 7-9 では、公開のtoastフィールドと、非公開のseasonal_fruitフィールドをもつ公開のback_of_house::Breakfast構造体を定義しました。 これは、例えば、レストランで、お客さんが食事についてくるパンの種類は選べるけれど、食事についてくるフルーツは季節と在庫に合わせてシェフが決める、という状況をモデル化しています。 提供できるフルーツはすぐに変わるので、お客さんはフルーツを選ぶどころかどんなフルーツが提供されるのか知ることもできません。

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
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() {
    // Order a breakfast in the summer with Rye toast
    // 夏 (summer) にライ麦 (Rye) パン付き朝食を注文
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Change our mind about what bread we'd like
    // やっぱり別のパンにする
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // The next line won't compile if we uncomment it; we're not allowed
    // to see or modify the seasonal fruit that comes with the meal
    // 下の行のコメントを外すとコンパイルできない。食事についてくる
    // 季節のフルーツを知ることも修正することも許されていないので
    // meal.seasonal_fruit = String::from("blueberries");
}
}

Listing 7-9: 公開のフィールドと非公開のフィールドとを持つ構造体

back_of_house::Breakfasttoastフィールドは公開されているので、eat_at_restaurantにおいてtoastをドット記法を使って読み書きできます。 seasonal_fruitは非公開なので、eat_at_restaurantにおいてseasonal_fruitは使えないということに注意してください。 seasonal_fruitを修正している行のコメントを外して、どのようなエラーが得られるか試してみてください!

また、back_of_house::Breakfastは非公開のフィールドを持っているので、Breakfastのインスタンスを作成 (construct) する公開された関連関数が構造体によって提供されている必要があります(ここではsummerと名付けました)。 もしBreakfastにそのような関数がなかったら、eat_at_restaurantにおいて非公開であるseasonal_fruitの値を設定できないので、Breakfastのインスタンスを作成できません。

一方で、enumを公開すると、そのヴァリアントはすべて公開されます。 Listing 7-10 に示されているように、pubenumキーワードの前にだけおけばよいのです。

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
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;
}
}

Listing 7-10: enumを公開に指定することはそのヴァリアントをすべて公開にする

Appetizerというenumを公開したので、SoupSaladというヴァリアントもeat_at_restaurantで使えます。 enumはヴァリアントが公開されてないとあまり便利ではないのですが、毎回enumのすべてのヴァリアントにpubをつけるのは面倒なので、enumのヴァリアントは標準で公開されるようになっているのです。 構造体はフィールドが公開されていなくても便利なことが多いので、構造体のフィールドは、pubがついてない限り標準で非公開という通常のルールに従うわけです。

まだ勉強していない、pubの関わるシチュエーションがもう一つあります。モジュールシステムの最後の機能、useキーワードです。 use自体の勉強をした後、pubuseを組み合わせる方法についてお見せします。

関連キーワード:  モジュール, waitlist, 関数, pub, restaurant, hosting, 公開, front, パス, Listing