Rust:Separating Modules into Different Files
分割前
- src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
front_of_houseをsrc/front_of_house.rsに移動する。
- src/lib.rs
mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
- src/front_of_house.rs
pub mod hosting { pub fn add_to_waitlist() {} }
モジュールツリー内で、mod
定義は一度だけで良い。一度コンパイラがそのファイルがプロジェクトの一部であることを認識すると、他のファイルは参照する際に、そのパスを指定するだけで良い。言い換えるとmod
は他の言語のinclude
ではない。
hostingモジュールを/src/front_of_house/hosting.rsに移動する
- src/front_of_house.rs
pub mod hosting;
- src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
Rust:Bringing Paths into Scope with the use Keyword
Bringing Paths Into Scope with the use Keyword - The Rust Programming Language
関数を呼び出す時にpathを全部書くのは不便だと感じるかもしれない。
use
キーワードでpathへのショートカットを作成できる。
crate rootに、use crate::front_of_house::hosting;
を追加した場合、そのスコープ内でhosting
が有効になる。
use
はuse
が記述されたスコープでのみ有効になるため、以下はエラーとなる。
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; mod customer { pub fn eat_at_restaurant() { hosting::add_to_waitlist(); } }
問題を解決するには、use
をcustomer
の中に移動させるか、super::hosting
で親モジュールから参照させる。
慣用的なuseの使い方
use
で関数をスコープに持ち込む場合は、関数名まで持ち込むのではなく親モジュールを持ち込む。
こうすることでその関数がどのモジュールで定義されているかが明確になる。
一方、構造体、enum、その他のitemを持ち込む場合は、フルパスを指定する。
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
このidiomに強い理由があるわけではないが、Rustのコードはこの形で読み書きされることに慣れている。
また、例外は同じ名前の2つのitemをスコープに持ち込む場合である。以下は異なる親モジュールを持つが、同じ名前のResult
型をスコープに持ち込む方法である。
use std::fmt; use std::io; fn function1() -> fmt::Result { // --snip-- } fn function2() -> io::Result<()> { // --snip-- }
asキーワードで新しい名前を与える
同じ名前の2つの型をスコープに持ち込む別の方法として、as
キーワードで新しいローカル名をつけることができる。
use std::fmt::Result; use std::io::Result as IoResult; fn function1() -> Result { // --snip-- } fn function2() -> IoResult<()> { // --snip-- }
pub useによる名前の再公開
use
ーワードで名前をスコープに持ち込むと、その名前は新しいスコープ内で非公開(プライベート)になる。pub
とuse
を組み合わせることで、その名前をあたかもそのコード内で定義されたかのように参照できるようにすることができる。この手法は「再公開(re-exporting)」と呼ばれる。
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
pub use
にする前にadd_to_waitlist()
呼び出すには、外部コードはrestaurant::front_of_house::hosting::add_to_waitlist()
と指定する必要があり、またfront_of_house
をpublicにする必要があった。pub use
を使うことで、hosting
モジュールが再公開されたため、restaurant::hosting::add_to_waitlist()
で使用が可能になる。
外部パッケージの利用
rand
パッケージを使用する際、Cargo.tomlに rand = "0.8.5"
を追加した。
Cargoはcrates.ioからrand
パッケージとその依存関係をダウンロードし、我々のプロジェクトで使用できるようにする。
その後、rand
の定義をプロジェクトのスコープに取り込むために、以下のように use 文を追加する。ここでは、クレート名 rand を先頭に指定し、スコープに取り込みたいアイテムを列挙する。Rngトレイトをスコープに取り込み、rand::thread_rng 関数を呼び出す。
use rand::Rng; fn main() { let secret_number = rand::thread_rng().gen_range(1..=100); }
標準ライブラリstd
も我々のパッケージから見ると外部クレートの1つである。ただ、Rust言語に付属しているため、Cargo.tomlにstd
を記載する必要はない。しかし、itemをスコープに取り込む場合は、use
が必要になる。
use std::collections::HashMap;
ネストしたpathで巨大なuseリストを整理する
// --snip-- use std::cmp::Ordering; use std::io; // --snip--
以下のように書き換えることができる。
// --snip-- use std::{cmp::Ordering, io}; // --snip--
ネストしたpathは、pathのどのレベルでも使用できる。
use std::io; use std::io::Write;
↓
use std::io::{self, Write};
glob演算子
use std::collections::*;
glob演算子を使うと、どの名前がスコープ内にあり、プログラムで使用される名前がどこで定義されたのかが分かりづらくなるため、注意が必要である。
Rust:Paths for Referring to an Item in the Module Tree
Paths for Referring to an Item in the Module Tree - The Rust Programming Language
pathには2つの形式がある
絶対パスはcrate rootからのフルパス。外部crateの場合は、crate名から始め、current crateの場合は
crate
から始める相対パスは
self
、super
またはcurrent module内の識別子を使用し、current moduleから始める
どちらも::
で区切る。
相対パスを使用するか絶対パスを使用するかは、プロジェクトに応じて決定する。 項目を定義しているコードと、それを使用しているコードを別々に移動する可能性が高いか、一緒に移動する可能性が高いかによって決まる。 一般的には、コード定義と項目の呼び出しを独立して移動させる可能性が高いため、絶対パスを指定することを推奨する。
pubキーワードによるPathの公開
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(); }
hosting
がprivateのためエラーとなる。Rustでは全てのitemはデフォルトで親モジュールからはprivateになる。関数や構造体のようなitemをprivateにしたい場合は、モジュール内に配置する。
親モジュールは子モジュールのプライベートitemは使用できない、しかし子モジュールはその祖先 のitemを使用することができる。
Rustがこのモジュールシステムを選択したのは、内部実装の詳細をデフォルトで隠蔽するためである。これにより、外部コードを破壊することなく、内部コードのどこを変更できるのかを知ることができる。
ただし、pub
キーワードを使用して、子モジュールのitemに外部の祖先モジュールからアクセス可能にできる。
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(); }
mod hosting
にpub
キーワドを付けただけでは、まだエラーとなる。moduleにmod
キーワードを付けた場合、そのモジュールにアクセスできるだけで、内部コードにはアクセスできない。モジュールはコンテナなので、更にitemもpublicにする必要がある。
なお、eat_at_restaurant
とfront_of_house
は同じモジュール内に定義されている(兄弟関係)ため、pub
は不要である。
Best Practices for Packages with a Binary and a Library
パッケージにはバイナリークレートとライブラリークレートの両方を含めることができる。一般的に、このようなパッケージパターンの場合は、バイナリークレートはライブラリークレートを呼び出すだけのコードにする。このようにすることでライブラリークレートのコードが共有できるため、多くの機能を他のプロジェクトで利用できる。
superによる相対パス
ファイルシステムの..
表記のように、super
を使用することで、親モジュールからの相対パスを定義できる。
構造体とEnumをpublicにする
構造体とenumにもpub
を使用した場合、その構造がpublicになるだけで、フィールドはprivateのままになる。それぞれのフィールドをpublicにするか否かを選択できる。
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"); }
privateフィールドを持つ構造体の場合、コンストラクタが必要である。なぜなら、privateフィールドには値をセットすることができず、インスタンを生成できないため。
対照的に、enumをpublicにした場合は、全てのvariantがpublicになる。
Rust:Defining Modules to Control Scope and Privacy
Defining Modules to Control Scope and Privacy - The Rust Programming Language
Modules Cheat Sheet
- Start from crate root
コンパイラは、最初にcrate rootファイルを検査しコンパイルする。
- module定義
crate rootファイルにmoduleを定義することができる。 "garden"moduleを定義した場合、コンパイラはmoduleを以下の場所から探す。
- インラインで記述された`mod module {}`
- src/garden.rs
- src/garden/mod.rs
- submodule定義
crate rootとは別のファイルに、submoduleを定義することができる。
例えばsrc/garden.rsにmod vegetables;
と定義した場合、コンパイラはsubmoduleを親moduleのディレクトリ名の中の以下の場所探す。
- インラインで記述された`mod vegetables{}`
- src/garden/vegetables.rs
- src/garden/vegetables/mod.rs
- module内のコードへのPath
一度moduleがcrateの一部になると、プライバシールールに従う限り、crateと同様にどこからでも参照することが可能になる。
例えば、garden vegetables moduleにAsparagus
型がある場合、crate::garden::vegetables::Asparagas
で参照できる。
- Private vs. public
module内のコードはデフォルトでは、親moduleからはプライベートになる。
moduleをpublicにするには、mod
の代わりにpub mod
を使用する。
public module内のitemをpublicにするする場合も、その定義の前にpub
を使用する。
use
キーワード
スコープ内で、use
キーワードはitemへのショートカット作成する。
crate::garden::vegetables::Asparasug
を参照できるスコープ内で、use crate::garden::vegetables::Asparagus;
とすると、Asparagus
だけで使用できる。
module内で関連するコードのグルーピング
module内には、さらに別のmoduleを配置することができる。またmoduleは構造体、enum、定数、トレイトや関数などの別のitemを配置することができる。
Rust:Packages and Crates
Packages and Crates - The Rust Programming Language
crateは、Rustコンパイラが1回に扱うコードの最小単位である。
単一のソースコードをrustc
でコンパイルする際も、そのファイルをcrateとして扱う。
crateはmoduleを含むことができ、他のファイルに定義されているmoduleも、crateと合わせてコンパイルされる。
crateには2つの形式がある。
Binary crates
- コマンドラインやサーバーで実行可能なプログラム
main
関数が必須
Library crates
main
関数を持たず、実行可能な形ではコンパイルされない- 複数のプロジェクトで共有されるように機能を定義したもの
- Rustaceansがcareteというときは、多くの場合Library cratesを指す
crate rootは、Rustコンパイラーが処理を開始するソース・ファイルのことであり、crateのroot moduleを構成する。
packageは1つ以上のcrateをまとめたもので、 一連の機能を提供する。 packageはビルド方法を定義したCargo.tomlファイルを含む。 Cargoもコマンドラインツールのbinary crateを含むpackageである。
Packageは複数のbinary crateを含むことができるが、library crateは最大で1つだけである。 packageには少なくとも1つのcrateが必要である。
binary crateの場合は、src/main.rsを、library crateの場合は、src/lib.rsをcrate rootと見なす。