モジュールツリーの要素を示すためのパス
ファイルシステムの中を移動する時と同じように、Rustにモジュールツリー内の要素を見つけるためにはどこを探せばいいのか教えるためにパスを使います。 関数を呼び出したいなら、そのパスを知っていなければなりません。
パスは2つの形を取ることができます:
- 絶対パス は、クレートの名前か
crate
という文字列を使うことで、クレートルートからスタートします。 - 相対パス は、
self
、super
または今のモジュール内の識別子を使うことで、現在のモジュールからスタートします。
絶対パスも相対パスも、その後に一つ以上の識別子がダブルコロン(::
)で仕切られて続きます。
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();
}
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.
エラーメッセージは、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 のコードも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.
何が起きたのでしょう?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() {}
これでこのコードはコンパイルできます!
絶対パスと相対パスをもう一度確認して、どうしてpub
キーワードを追加することでadd_to_waitlist
のそれらのパスを使えるようになるのか、プライバシールールの観点からもう一度確認してみてみましょう。
絶対パスは、クレートのモジュールツリーのルートであるcrate
から始まります。
クレートルートの中にfront_of_house
が定義されています。
front_of_house
は公開されていませんが、eat_at_restaurant
関数はfront_of_house
と同じモジュール内で定義されている(つまり、eat_at_restaurant
とfront_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
が定義されている場所からの相対パスが使えます。
そして、hosting
とadd_to_waitlist
はpub
が付いていますから、残りのパスについても問題はなく、この関数呼び出しは有効というわけです。
相対パスを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() {}
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"); } }
back_of_house::Breakfast
のtoast
フィールドは公開されているので、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 に示されているように、pub
はenum
キーワードの前にだけおけばよいのです。
ファイル名: 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; } }
Appetizer
というenumを公開したので、Soup
とSalad
というヴァリアントもeat_at_restaurant
で使えます。
enumはヴァリアントが公開されてないとあまり便利ではないのですが、毎回enumのすべてのヴァリアントにpub
をつけるのは面倒なので、enumのヴァリアントは標準で公開されるようになっているのです。
構造体はフィールドが公開されていなくても便利なことが多いので、構造体のフィールドは、pub
がついてない限り標準で非公開という通常のルールに従うわけです。
まだ勉強していない、pub
の関わるシチュエーションがもう一つあります。モジュールシステムの最後の機能、use
キーワードです。
use
自体の勉強をした後、pub
とuse
を組み合わせる方法についてお見せします。