久しぶりに Rust を書いたらモジュール周り全然思い出せなかったのでメモ。
以下の公式ドキュメントの第7章を自分用にまとめつつ、調べたことを足した内容になります。
The Rust Programming Language 日本語版 7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する
パッケージ、クレート、モジュール
- パッケージ:1つ以上のクレートを持ち、ある機能を提供するための単位
- 1つの
Cargo.toml
で管理する粒度 - 0個か1個のライブラリクレートを持つ
- バイナリクレートはいくらでも持って良い
- ライブラリクレートとバイナリクレートをあわせて少なくとも1つのクレートを持っていないといけない
- 1つの
- クレート:ライブラリ(
lib.rs
)か実行可能ファイル(main.rs
)を生成するモジュール群- ライブラリクレート:
src/lib.rs
- バイナリクレート:
src/main.rs
- または、
src/bin
ディレクトリにファイルを置くことで複数のバイナリクレートを持つことができる
- または、
- ライブラリクレート:
- モジュール:
mod
やuse
でまとめる単位
ので、ディレクトリ構成としては以下のようになる(後述するモジュール単位のファイルを除く)
<package> - Cargo.toml - src/ - lib.rs ... 0 or 1 - main.rs ... 0 or 1 - bin/ ... 0 or N (files) - bin_a.rs - bin_b.rs - ...
モジュールの基本的なルール
mod
キーワードでくくった範囲がモジュールとなり、スコープが分かれる
mod front_of_house { mod hosting { fn add_to_waitlist() {} } } mod back_of_house {}
これは、以下のようなモジュールツリーを形成する。
crate - front_of_house - hosting - add_to_waitlist - back_of_house
モジュールはパスを指定して呼び出す
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(); self::front_of_house::hosting::add_to_waitlist(); }
モジュールはデフォルトで非公開。公開するには pub
キーワードをつける
- モジュールの公開範囲と、モジュールの中身の公開範囲は別々に指定する
mod front_of_house { pub mod hosting { // ここもpubにしないとadd_to_waitlist()関数にはアクセスできない pub fn add_to_waitlist() {} } }
- 構造体は各フィールドを個別に公開するかどうか決められる
- enum は公開するとそのヴァリアントはすべて公開される
mod back_of_house { pub struct Breakfast { pub toast: String, seasonal_fruit: String, // このフィールドにはアクセスできない } pub enum Appetizer { Soup, Salad, } } pub fn eat_at_restaurant() { let meal = back_of_house::Breakfast { toast: String::from("Rye"), seasonal_fruit: String::from("blueberries"), // error }; let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad; }
use
キーワード
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; pub fn eat_at_restaurant() { // before: // crate:front_of_house::hosting::add_to_waitlist(); hosting::add_to_waitlist(); }
- 慣例的に、
- 関数を
use
で持ち込む場合、親モジュールをuse
してsome_module::func()
と書く- パスを省略しつつ関数がローカルで定義されていないことを明らかにできるため
- 構造体やenumを
use
で持ち込む場合、フルパスを書く
- 関数を
as
で別名をつけることもできる。モジュール間で名前の衝突を回避するときなどに使える
use std::fmt::Result; use std::io::Result as IoResult;
pub use
を使うと名前を再公開(re-exporting)できる- 下の例では、このライブラリクレートを使う外部のコードも
hosting::add_to_waitlist()
を使えるようになる
- 下の例では、このライブラリクレートを使う外部のコードも
// src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting;
モジュールを複数のファイルに分割する
https://doc.rust-jp.rs/book-ja/ch07-05-separating-modules-into-different-files.html
src/lib.rs
にmod xxx;
という宣言だけ書くと、モジュールの中身はsrc/xxx.rs
というファイルを見に行くようになる
// src/lib.rs mod front_of_house; pub use crate::front_of_house::hosting;
// src/front_of_house.rs pub mod hosting { pub fn add_to_waitlist() {} }
- さらに、
src/xxx.rs
内でmod yyy;
という宣言だけ書くと、モジュールの中身はsrc/xxx/yyy.rs
というファイルを見に行くようになる
// src/lib.rs mod front_of_house; pub use crate::front_of_house::hosting;
// src/front_of_house.rs pub mod hosting;
// src/front_of_house/hosting.rs pub fn add_to_waitlist() {}
というわけで、ディレクトリ構成は以下のようになりそう。
- src/ - lib.rs - module_a.rs # サブモジュール含む - module_a/ - sub_module_a.rs - module_b.rs # サブモジュール含まない - ...
- モジュールツリーとしては変わらないので、使う側の
use
宣言や呼び出し方は何も変わらない
lib.rs
で定義したモジュールを main.rs
で使うには?
ドキュメントでは触れられていなかった部分。パスをパッケージ名から始める必要がある。
// Cargo.toml [package] name = "restaurant" version = "0.1.0"
// src/main.rs use restaurant::hosting; fn main() { hosting::add_to_waitlist(); }
こうすると使えるようになるが、根拠となるドキュメントは見つけられてない。
Stackoverflow のこれは若干関係しそう。
Rust modules confusion when there is main.rs and lib.rs - Stack Overflow
余談: mod.rs
「Rust モジュールシステム」とかでググると、モジュールを xxx/mod.rs
というファイルで定義する方法が紹介されてる記事が見つかるが、
Rustのモジュールの使い方 2018 Edition版 | κeenのHappy Hacκing Blog
を読む限り、これは 2015 Edition の頃の後方互換性のために用意されているらしい。
これ以上深追いはせず、素直に公式ドキュメントで案内されてる方法を使うことにする。