モジュールツリー内のアイテムを参照するためのパス
Rust に対してモジュールツリー内のどこにアイテムがあるかを示すには、 ファイルシステムを移動するときにパスを使うのと同じようにパスを使います。関数を呼び出すには、 そのパスを知っている必要があります。
パスには 2 つの形式があります。
- 絶対パス はクレートルートから始まる完全なパスです。外部クレートの
コードに対しては、絶対パスはクレート名から始まり、現在のクレートの
コードに対しては、リテラルの
crateから始まります。 - 相対パス は現在のモジュールから始まり、
self、super、または 現在のモジュール内の識別子を使います。
絶対パスと相対パスのどちらも、二重コロン (::) で区切られた 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 についてさらに詳しく説明します。
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 キーワードを
使えます。その後、add_to_waitlist にたどり着くまで、順に各モジュールを含めていきます。
同じ構造のファイルシステムを想像してみてください。add_to_waitlist
プログラムを実行するには、/front_of_house/hosting/add_to_waitlist という
パスを指定するはずです。クレートルートから始めるためにクレート名を使うことは、
シェルでファイルシステムルートから始めるために / を使うのに似ています。
eat_at_restaurant で 2 回目に add_to_waitlist を呼び出すときには、
相対パスを使います。パスは front_of_house から始まります。これは
eat_at_restaurant と同じレベルのモジュールツリーで定義されているモジュール名です。
ファイルシステムでこれに相当するのは、
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
エラーメッセージは、モジュール hosting が非公開だと伝えています。言い換えると、
hosting モジュールと add_to_waitlist 関数へのパス自体は正しいものの、
Rust は非公開の部分にアクセスできないため、それらを使わせてくれません。Rust では、
すべてのアイテム(関数、メソッド、構造体、列挙型、モジュール、定数)は、
デフォルトでは親モジュールに対して非公開です。関数や構造体のようなアイテムを
非公開にしたいなら、それをモジュールに入れます。
親モジュール内のアイテムは子モジュール内の非公開アイテムを使えませんが、 子モジュール内のアイテムは祖先モジュール内のアイテムを使えます。これは、 子モジュールがその実装の詳細を包み隠す一方で、子モジュール自身は 定義されている文脈を見ることができるからです。この比喩を続けるなら、 プライバシールールはレストランのバックオフィスのようなものだと考えてください。 その中で起こっていることはレストランの客には非公開ですが、 事務所の管理者は自分が運営するレストランのすべてを見たり行ったりできます。
Rust がモジュールシステムをこのように機能させるよう選んだのは、
内部実装の詳細を隠すことがデフォルトになるようにするためです。そうすることで、
外側のコードを壊さずに内部コードのどの部分を変更できるかがわかります。
しかし Rust では、pub キーワードを使ってアイテムを公開にすることで、
子モジュールのコードの内部部分を外側の祖先モジュールに公開するという選択肢も用意されています。
pub キーワードでパスを公開する
hosting モジュールが非公開だと教えてくれたリスト 7-4 のエラーに戻りましょう。
親モジュール内の eat_at_restaurant 関数から、子モジュール内の
add_to_waitlist 関数にアクセスできるようにしたいので、リスト 7-5 に示すように
hosting モジュールに pub キーワードを付けます。
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
// -- snip --
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 のコードでも、リスト 7-6 に示すように コンパイラエラーが発生します。
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:10:37
|
10 | 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:13:30
|
13 | 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
何が起きたのでしょうか。mod hosting の前に pub キーワードを付けると、
そのモジュールは公開になります。この変更により、front_of_house にアクセスできれば、
hosting にもアクセスできます。しかし、hosting の 中身 は依然として
非公開です。モジュールを公開にしても、その中身まで公開になるわけではありません。
モジュールに付いた pub キーワードは、祖先モジュール内のコードがそのモジュールを
参照できるようにするだけで、その内部コードにアクセスできるようにするものではありません。
モジュールはコンテナなので、モジュールを公開にするだけでできることはあまりありません。
さらに進んで、そのモジュール内の 1 つ以上のアイテムも公開にする必要があります。
リスト 7-6 のエラーは、add_to_waitlist 関数が非公開だと言っています。
プライバシールールは、モジュールだけでなく、構造体、列挙型、関数、メソッドにも適用されます。
add_to_waitlist 関数も、リスト 7-7 のように、その定義の前に pub
キーワードを追加して公開にしましょう。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
これでコードはコンパイルできるようになりました! なぜ pub キーワードを
追加すると、可視性ルールに照らして eat_at_restaurant でこれらのパスを使える
ようになるのかを理解するために、絶対パスと相対パスを見ていきましょう。
絶対パスでは、クレートのモジュールツリーのルートである 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の変更を管理する際には、 考慮すべき点が数多くあります。これらの考慮事項はこの本の範囲を超える ので、この話題に興味があるなら、Rust API ガイドラインを 参照してください。
バイナリとライブラリを持つパッケージのベストプラクティス
既に説明したように、パッケージには src/main.rs のバイナリクレートの ルートと src/lib.rs のライブラリクレートのルートの両方を含めることが でき、どちらのクレートもデフォルトではパッケージ名を持ちます。通常、 ライブラリとバイナリクレートの両方を含むこのパターンのパッケージでは、 バイナリクレートには実行可能ファイルを起動し、ライブラリクレートで 定義されたコードを呼び出すのに必要な最小限のコードだけを置きます。 こうすることで、ライブラリクレートのコードを共有できるため、その パッケージが提供する機能の大部分を他のプロジェクトでも利用できる ようになります。
モジュールツリーは src/lib.rs で定義すべきです。そうすれば、公開された アイテムはすべて、パスをパッケージ名から始めることでバイナリクレート から使用できます。バイナリクレートは、完全に外部のクレートが ライブラリクレートを使うのと同じように、ライブラリクレートの利用者に なります。つまり、公開APIしか使えません。これは良いAPIを設計する助けに なります。というのも、あなたは作者であるだけでなく、クライアントでも あるからです!
第12章 では、この構成方法を、バイナリクレートと ライブラリクレートの両方を含むコマンドラインプログラムで実演します。
super で始まる相対パス
現在のモジュールやクレートルートではなく、親モジュールから始まる相対パスを
構築するには、パスの先頭に super を使います。これは、親ディレクトリへ
移動することを意味する .. 構文でファイルシステムのパスを始めるのに似て
います。super を使うことで、親モジュールにあると分かっているアイテムを
参照できるため、あるモジュールが親と密接に関係している一方で、その親が
将来モジュールツリー内の別の場所へ移されるかもしれない場合に、モジュール
ツリーの再編成が容易になることがあります。
Listing 7-8 のコードは、シェフが間違った注文を修正し、自ら客のところへ
運ぶ状況をモデル化したものです。back_of_house モジュールで定義された
fix_incorrect_order 関数は、super で始まる deliver_order への
パスを指定することで、親モジュールで定義された deliver_order 関数を
呼び出します。
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
fix_incorrect_order 関数は back_of_house モジュール内にあるので、
super を使って back_of_house の親モジュール、この場合はルートである
crate に移動できます。そこから deliver_order を探すと見つかります。
うまくいきます! back_of_house モジュールと deliver_order 関数は、
互いに対して同じ関係のまま保たれ、クレートのモジュールツリーを再編成する
ことになっても一緒に移動される可能性が高いと考えられます。そのため、この
コードが将来別のモジュールに移された場合でも更新すべき箇所が少なくて済む
ように、super を使いました。
構造体と列挙型を公開する
pub を使って構造体や列挙型を公開にすることもできますが、構造体や列挙型に
対する pub の使い方にはいくつか追加の細かい点があります。構造体定義の前に
pub を付けると、その構造体自体は公開になりますが、構造体のフィールドは
引き続き非公開のままです。各フィールドを公開にするかどうかは、
ケースバイケースで決められます。Listing 7-9 では、公開された toast
フィールドと非公開の seasonal_fruit フィールドを持つ、公開の
back_of_house::Breakfast 構造体を定義しています。これはレストランで、
客は食事に付くパンの種類は選べるが、どの果物を添えるかは旬かどうかや
在庫状況に基づいてシェフが決める、という状況を表しています。提供される
果物はすぐに変わるので、客は果物を選べず、どの果物が出てくるのかを
見ることさえできません。
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.
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 のインスタンスを構築する公開の関連関数を構造体側で提供する
必要があります(ここでは summer という名前にしています)。Breakfast に
そのような関数がなければ、eat_at_restaurant では Breakfast の
インスタンスを作成できません。なぜなら、eat_at_restaurant では非公開の
seasonal_fruit フィールドの値を設定できないからです。
対照的に、列挙型を公開にすると、そのすべてのバリアントも公開になります。
Listing 7-10 に示すように、必要なのは enum キーワードの前に pub を
付けることだけです。
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 を公開にしたので、eat_at_restaurant では Soup と Salad
のバリアントを使えます。
バリアントが公開でなければ、enum はあまり有用ではありません。あらゆる場合に
すべての enum バリアントへ pub を付けなければならないのは煩雑なので、enum
バリアントはデフォルトで公開です。一方、struct はフィールドが公開でなくても
有用なことがよくあるため、struct のフィールドは、pub で注釈しない限り
すべてがデフォルトで非公開であるという一般的な規則に従います。
まだ扱っていない pub が関わる状況がもう 1 つあり、それがモジュール
システムにおける最後の機能である use キーワードです。まずは use 単体を
取り上げ、その後で pub と use を組み合わせる方法を示します。