モジュールツリーの要素を示すためのパス
ファイルシステムの中を移動する時と同じように、Rustにモジュールツリー内の要素を見つけるためにはどこを探せばいいのか教えるためにパスを使います。 関数を呼び出すためには、そのパスを知っていなければなりません。
パスは2つの形を取ることができます:
- 絶対パス は、クレートルートを起点とするフルパスです;
絶対パスは、外部のクレートに属するコードに関してはそのクレート名で始まり、現在のクレートに属するコードに関してはリテラル
crateで始まります。 - 相対パス は、現在のモジュールを起点とし、
self、super、または今のモジュール内の識別子を使います。
絶対パスも相対パスも、その後に一つ以上の識別子がダブルコロン(::)で仕切られて続きます。
リスト7-1に戻ってみて、例えばadd_to_waitlist関数を呼びたいとしましょう。
これはこう聞くのと同じです: add_to_waitlistのパスは何でしょうか?
リスト7-3は、リスト7-1からいくつかのモジュールと関数を削除したものを含んでいます。
これを使って、クレートルートに定義された新しい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();
}
リスト7-3: add_to_waitlist 関数を絶対パスと相対パスで呼び出す
eat_at_restaurantで最初にadd_to_waitlist関数を呼び出す時、絶対パスを使っています。
add_to_waitlist関数はeat_at_restaurantと同じクレートで定義されているので、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への絶対パスは同じままですが、相対パスは更新しないといけないでしょう。
一般論としては、コードの定義と、その要素の呼び出しは独立に動かしたいことが多いと思われるので、絶対パスのほうが好ましいです。
では、リスト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();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| 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();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
リスト7-4: リスト7-3のコードをビルドしたときのコンパイルエラー
エラーメッセージは、hostingは非公開 (private) だ、と言っています。
言い換えるなら、hostingモジュールとadd_to_waitlist関数へのパスは正しいが、非公開な部分へのアクセスは許可されていないので、Rustがそれを使わせてくれないということです。
Rustでは、すべての要素(関数、メソッド、構造体、enum、モジュール、そして定数)はデフォルトでは親モジュールに対して非公開です。
関数や構造体などの要素を非公開にしたければ、モジュールの中に置いてください。
親モジュールの要素は子モジュールの非公開要素を使えませんが、子モジュールの要素はその祖先モジュールの要素を使えます。 これは、子モジュールは実装の詳細を覆い隠しますが、子モジュールは自分の定義された文脈を見ることができるためです。 レストランの喩えを続けるなら、レストランの後方部門になったつもりでプライバシーのルールを考えてみてください。レストランの顧客にはそこで何が起こっているのかは非公開ですが、そこを運営するオフィスマネージャには、レストランのことは何でも見えるし何でもできるのです。
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() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
// 絶対パス
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
// 相対パス
front_of_house::hosting::add_to_waitlist();
}
リスト7-5: hosting モジュールを pub として宣言することでeat_at_restaurantから使う
残念ながら、リスト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() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
リスト7-6: リスト7-5のコードをビルドしたときのコンパイルエラー
何が起きたのでしょう?pubキーワードをmod hostingの前に追加したことで、このモジュールは公開されました。
この変更によって、front_of_houseにアクセスできるなら、hostingにもアクセスできるようになりました。
しかしhostingの 中身 はまだ非公開です。モジュールを公開してもその中身は公開されないのです。
モジュールにpubキーワードがついていても、祖先モジュールのコードはモジュールを参照できるようになるだけで、その内部のコードへのアクセスは許可されません。
モジュールはコンテナなので、モジュールを公開しただけでは、できることはあまりありません;
さらに先へ進み、モジュール内のひとつまたは複数の要素も公開することを選択する必要があります。
リスト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();
}
リスト7-7: pubキーワードをmod hostingとfn 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_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が付いていますから、残りのパスについても問題はなく、この関数呼び出しは有効というわけです。
他のプロジェクトからあなたのコードを利用できるようにライブラリクレートを共有するつもりなら、 公開APIは、クレート利用者があなたのコードとどう相互作用できるかを決定する、クレート利用者との契約となります。 人々があなたのクレートに依存しやすくするためには、公開APIへの変更の管理に関する多数の考慮事項があります。 これらの考慮事項はこの本のスコープ外です; このトピックに興味がある場合は、The Rust API Guidelinesを参照してください。
バイナリとライブラリの両方を持つパッケージでのベストプラクティス
パッケージはsrc/main.rsバイナリクレートルートとsrc/lib.rsライブラリクレートルートの両方を含むことができ、 デフォルトでは両方のクレートがパッケージ名を持つことを説明しました。 典型的には、ライブラリとバイナリのクレート両方を含むこのパターンのパッケージでは、バイナリクレートには ライブラリクレートのコードを呼び出して、実行可能形式を開始するために必要な最低限のコードを持たせます。 こうすることで、ライブラリクレートのコードは共有できるので、 他のプロジェクトはこのパッケージが提供するほとんどの機能を活用することができます。
モジュールツリーはsrc/lib.rs内に定義してください。 その場合、バイナリクレートからは、パッケージ名でから始まるパスを使って公開された要素を使用することができます。 バイナリクレートは、そのライブラリクレートを利用する完全に外部のクレートとまったく同じように、 ライブラリクレートの利用者になります: 公開APIしか使用することができません。 これは良いAPIを設計する助けになります; あなたは作者であるだけでなく、利用者にもなるのです!
第12章では、バイナリクレートとライブラリクレートの両方を含むコマンドラインプログラムを使って、 この整理法の実践を示します。
相対パスをsuperで始める
現在のモジュールやクレートルートではなく、親モジュールから始まる相対パスなら、superを最初につけることで構成できます。
ファイルシステムパスを..構文で始めるのに似ています。
superを使用することで、親モジュールにあることを知っている要素を参照することができます。
これにより、そのモジュールが親と密接に関連しているが、いつか親がモジュールツリー内のどこかに移動されるかもしれないという場合に、モジュールツリーを再編成するのがより簡単になるかもしれません。
シェフが間違った注文を修正し、自分でお客さんに持っていくという状況をモデル化している、リスト7-8を考えてみてください。
back_of_houseモジュールで定義されているfix_incorrect_order関数は、親モジュールで定義されているdeliver_order関数を呼び出すために、superから始まる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を使うことで、将来このコードが別のモジュールに移動するとしても、更新する場所が少なくて済むようにしました。
構造体とenumを公開する
構造体やenumもpubを使って公開するよう指定できますが、構造体とenumでの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() {
// 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");
}
リスト7-9: 公開のフィールドと非公開のフィールドとを持つ構造体
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を公開すると、そのヴァリアントはすべて公開されます。
リスト7-10に示されているように、pubはenumキーワードの前にだけおけばよいのです。
ファイル名: 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: enumを公開に指定することはそのヴァリアントをすべて公開にする
Appetizerというenumを公開したので、SoupとSaladというヴァリアントもeat_at_restaurantで使えます。
enumはヴァリアントが公開されてないとあまり便利ではないのですが、毎回enumのすべてのヴァリアントにpubをつけるのは面倒なので、enumのヴァリアントは標準で公開されるようになっているのです。
構造体はフィールドが公開されていなくても便利なことが多いので、構造体のフィールドは、pubがついてない限り標準で非公開という通常のルールに従うわけです。
まだ勉強していない、pubの関わるシチュエーションがもう一つあります。モジュールシステムの最後の機能、useキーワードです。
use自体の勉強をした後、pubとuseを組み合わせる方法についてお見せします。