注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
プロジェクトが大きくなり始めた際に、コードを小さなまとまりに分割しそれらでプロジェクトを組み立てるのはソフトウェア工学における優れた慣例だと考えられています。幾つかの機能をプライベートとし、また幾つかをパブリックとできるように、はっきりと定義されたインターフェースも重要となります。こういった事柄を容易にするため、Rustはモジュールシステムを有しています。
Rustはモジュールシステムに関連する「クレート」(crate)と「モジュール」(module)という2つの用語を明確に区別しています。クレートは他の言語における「ライブラリ」や「パッケージ」と同じ意味です。このことからRustのパッケージマネジメントツールの名前を「Cargo」としています。(訳注: crateとは枠箱のことであり、cargoは船荷を指します)Cargoを使ってクレートを出荷し他のユーザに公開するわけです。クレートは実行形式かライブラリをプロジェクトに応じて作成できます。
各クレートは自身のコードが入っている ルートモジュール (root module)を暗黙的に持っています。そしてルートモジュール下にはサブモジュールの木が定義できます。モジュールによりクレート内でコードを分割できます。
例として、 phrases クレートを作ってみます。これに異なる言語で幾つかのフレーズを入れます。問題の単純さを保つために、2種類のフレーズ「greetings」と「farewells」のみとし、これらフレーズを表すための2つの言語として英語と日本語を使うことにします。モジュールのレイアウトは以下のようになります。
+-----------+
+---| greetings |
| +-----------+
+---------+ |
+---| english |---+
| +---------+ | +-----------+
| +---| farewells |
+---------+ | +-----------+
| phrases |---+
+---------+ | +-----------+
| +---| greetings |
| +----------+ | +-----------+
+---| japanese |--+
+----------+ |
| +-----------+
+---| farewells |
+-----------+
この例において、 phrases
がクレートの名前で、それ以外の全てはモジュールです。それらが木の形をしており、クレートの ルート から枝分かれしていることが分かります。そして木のルートは phrases
それ自身です。
ここまでで計画は立ちましたから、これらモジュールをコードで定義しましょう。始めるために、Cargoで新しいクレートを生成します。
$ cargo new phrases
$ cd phrases
聡明な読者ならご記憶かと思いますがCargoが単純なプロジェクトを生成してくれます。
$ tree .
.
├── Cargo.toml
└── src
└── lib.rs
1 directory, 2 files
src/lib.rs
はクレートのルートであり、先程の図における phrases
に相当します。
それぞれのモジュールを定義するために、 mod
キーワードを使います。 src/lib.rs
を以下のようにしましょう。
mod english { mod greetings { } mod farewells { } } mod japanese { mod greetings { } mod farewells { } }
mod
キーワードの後、モジュールの名前を与えます。モジュール名はRustの他の識別子の慣例に従います。つまり lower_snake_case
です。各モジュールの内容は波括弧( {}
)の中に書きます。
与えられた mod
内で、サブ mod
を定義することができます。サブモジュールは2重コロン( ::
)記法で参照できます。ネストされた4つのモジュールは english::greetings
、 english::farewells
、 japanese::greetings
、そして japanese::farewells
です。これらサブモジュールは親モジュール配下の名前空間になるため、名前は競合しません。つまり english::greetings
と japanese::greetings
は例え両名が greetings
であったとしても、明確に区別されます。
このクレートは main()
関数を持たず、 lib.rs
と名付けられているため、Cargoはこのクレートをライブラリとしてビルドします。
$ cargo build
Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
$ ls target/debug
build deps examples libphrases-a7448e02a0468eaa.rlib native
libphrases-hash.rlib
はコンパイルされたクレートです。このクレートを他のクレートから使う方法を見る前に、複数のファイルに分割してみましょう。
各クレートがただ1つのファイルからなるのであれば、これらファイルは非常に大きくなってしまうでしょう。クレートを複数のファイルに分けた方が楽になるため、Rustは2つの方法でこれをサポートしています。
以下のようなモジュールを宣言する代わりに、
fn main() { mod english { // // contents of our module go here // モジュールの内容はここに } }mod english { // モジュールの内容はここに }
以下のようなモジュールが宣言できます。
fn main() { mod english; }mod english;
こうすると、Rustは english.rs
ファイルか、 english/mod.rs
ファイルのどちらかにモジュールの内容があるだろうと予想します。
それらのファイルの中でモジュールの再宣言を行う必要がないことに気をつけて下さい。先の mod
宣言にてそれは済んでいます。
これら2つのテクニックを用いて、クレートを2つのディレクトリと7つのファイルに分解できます。
$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── src
│ ├── english
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ ├── japanese
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ └── lib.rs
└── target
└── debug
├── build
├── deps
├── examples
├── libphrases-a7448e02a0468eaa.rlib
└── native
src/lib.rs
はクレートのルートで、以下のようになっています。
mod english; mod japanese;
これら2つの宣言はRustへ書き手の好みに合わせて src/english.rs
と src/japanese.rs
、または src/english/mod.rs
と src/japanese/mod.rs
のどちらかを見よと伝えています。今回の場合、サブモジュールがあるため私たちは後者を選択しました。 src/english/mod.rs
と src/japanese/mod.rs
は両方とも以下のようになっています。
mod greetings; mod farewells;
繰り返すと、これら宣言はRustへ src/english/greetings.rs
と src/japanese/greetings.rs
、または src/english/farewells/mod.rs
と src/japanese/farewells/mod.rs
のどちらかを見よと伝えています。これらサブモジュールは自身配下のサブモジュールを持たないため、私たちは src/english/greetings.rs
と src/japanese/farewells.rs
を選びました。ヒュー!
src/english/greetings.rs
と src/japanese/farewells.rs
の中身は現在両方とも空です。幾つか関数を追加しましょう。
src/english/greetings.rs
に以下を入力します。
fn hello() -> String { "Hello!".to_string() }
src/english/farewells.rs
に以下を入力します。
fn goodbye() -> String { "Goodbye.".to_string() }
src/japanese/greetings.rs
に以下を入力します。
fn hello() -> String { "こんにちは".to_string() }
勿論、このwebページからコピー&ペーストしたり、他の何かをタイプしても構いません。モジュールシステムを学ぶのに「konnichiwa」と入力するのは重要なことではありません。
src/japanese/farewells.rs
に以下を入力します。
fn goodbye() -> String { "さようなら".to_string() }
(英語だと「Sayōnara」と表記するようです、御参考まで。)
ここまででクレートに幾つかの機能が実装されました、それでは他のクレートから使ってみましょう。
前節でライブラリクレートができました。インポートしこのライブラリを用いた実行形式クレートを作成しましょう。
src/main.rs
を作成し配置します。(このコンパイルはまだ通りません)
extern crate phrases; fn main() { println!("Hello in English: {}", phrases::english::greetings::hello()); println!("Goodbye in English: {}", phrases::english::farewells::goodbye()); println!("Hello in Japanese: {}", phrases::japanese::greetings::hello()); println!("Goodbye in Japanese: {}", phrases::japanese::farewells::goodbye()); }
extern crate
宣言はRustにコンパイルして phrases
クレートをリンクせよと伝えます。するとこのクレート内で phrases
モジュールが使えます。先に述べていたように、2重コロンでサブモジュールとその中の関数を参照できます。
(注意: Rustの識別子として適切でない「like-this」のような、名前の中にダッシュが入ったクレートをインポートする場合、ダッシュがアンダースコアへ変換されるため extern crate like_this;
と記述します。)
また、Cargoは src/main.rs
がライブラリクレートではなくバイナリクレートのルートだと仮定します。パッケージには今2つのクレートがあります。 src/lib.rs
と src/main.rs
です。ほとんどの機能をライブラリクレート内に置き、実行形式クレートから利用するのがよくあるパターンです。この方法なら他のプログラムがライブラリクレートを使うこともできますし、素敵な関心の分離(separation of concerns)にもなります。
けれどこのままではまだ動作しません。以下に似た4つのエラーが発生します。
$ cargo build
Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
src/main.rs:4:38: 4:72 error: function `hello` is private
src/main.rs:4 println!("Hello in English: {}", phrases::english::greetings::hello());
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: in expansion of format_args!
<std macros>:2:25: 2:58 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
phrases/src/main.rs:4:5: 4:76 note: expansion site
デフォルトでは、Rustにおける全てがプライベートです。それではもっと詳しく説明しましょう。
Rustはインターフェースのパブリックである部分をきっちりと管理します。そのためプライベートがデフォルトです。パブリックにするためには pub
キーワードを使います。まずは english
モジュールに焦点を当てたいので、 src/main.rs
をこれだけに減らしましょう。
extern crate phrases; fn main() { println!("Hello in English: {}", phrases::english::greetings::hello()); println!("Goodbye in English: {}", phrases::english::farewells::goodbye()); }
src/lib.rs
内にて、 english
モジュールの宣言に pub
を加えましょう。
pub mod english; mod japanese;
また src/english/mod.rs
にて、両方とも pub
にしましょう。
pub mod greetings; pub mod farewells;
src/english/greetings.rs
にて、 fn
の宣言に pub
を加えましょう。
pub fn hello() -> String { "Hello!".to_string() }
また src/english/farewells.rs
にもです。
pub fn goodbye() -> String { "Goodbye.".to_string() }
これでクレートはコンパイルできますが、 japanese
関数が使われていないという旨の警告が発生します。
$ cargo run
Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
src/japanese/greetings.rs:1:1: 3:2 warning: function is never used: `hello`, #[warn(dead_code)] on by default
src/japanese/greetings.rs:1 fn hello() -> String {
src/japanese/greetings.rs:2 "こんにちは".to_string()
src/japanese/greetings.rs:3 }
src/japanese/farewells.rs:1:1: 3:2 warning: function is never used: `goodbye`, #[warn(dead_code)] on by default
src/japanese/farewells.rs:1 fn goodbye() -> String {
src/japanese/farewells.rs:2 "さようなら".to_string()
src/japanese/farewells.rs:3 }
Running `target/debug/phrases`
Hello in English: Hello!
Goodbye in English: Goodbye.
pub
は struct
や そのメンバのフィールドにも使えます。Rustの安全性に対する傾向に合わせ、単に struct
をパブリックにしてもそのメンバまでは自動的にパブリックになりません。個々のフィールドに pub
を付ける必要があります。
関数がパブリックになり、呼び出せるようになりました。素晴らしい!けれども phrases::english::greetings::hello()
を打つのは非常に長くて退屈ですね。Rustにはもう1つ、現在のスコープに名前をインポートするキーワードがあるので、それを使えば短い名前で参照できます。では use
について説明しましょう。
use
でモジュールをインポートするRustには use
キーワードがあり、ローカルスコープの中に名前をインポートできます。 src/main.rs
を以下のように変えてみましょう。
extern crate phrases; use phrases::english::greetings; use phrases::english::farewells; fn main() { println!("Hello in English: {}", greetings::hello()); println!("Goodbye in English: {}", farewells::goodbye()); }
2つの use
の行はローカルスコープの中に各モジュールをインポートしているため、とても短い名前で関数を参照できます。慣習では関数をインポートする場合、関数を直接インポートするよりもモジュール単位でするのがベストプラクティスだと考えられています。言い換えれば、こうすることも できる わけです。
extern crate phrases; use phrases::english::greetings::hello; use phrases::english::farewells::goodbye; fn main() { println!("Hello in English: {}", hello()); println!("Goodbye in English: {}", goodbye()); }
しかしこれは慣用的ではありません。名前の競合を引き起こす可能性が非常に高まるからです。この短いプログラムだと大したことではありませんが、長くなるにつれ問題になります。名前が競合するとコンパイルエラーになります。例えば、 japanese
関数をパブリックにして、以下を試してみます。
extern crate phrases; use phrases::english::greetings::hello; use phrases::japanese::greetings::hello; fn main() { println!("Hello in English: {}", hello()); println!("Hello in Japanese: {}", hello()); }
Rustはコンパイル時にエラーを起こします。
Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
src/main.rs:4:5: 4:40 error: a value named `hello` has already been imported in this module [E0252]
src/main.rs:4 use phrases::japanese::greetings::hello;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `phrases`.
同じモジュールから複数の名前をインポートする場合、二度同じ文字を打つ必要はありません。以下の代わりに、
fn main() { use phrases::english::greetings; use phrases::english::farewells; }use phrases::english::greetings; use phrases::english::farewells;
このショートカットが使えます。
fn main() { use phrases::english::{greetings, farewells}; }use phrases::english::{greetings, farewells};
pub use
による再エクスポートuse
は識別子を短くするためだけに用いるのではありません。他のモジュール内の関数を再エクスポートするためにクレートの中で使うこともできます。これを使って内部のコード構成そのままではない外部向けインターフェースを提供できます。
例を見てみましょう。 src/main.rs
を以下のように変更します。
extern crate phrases; use phrases::english::{greetings,farewells}; use phrases::japanese; fn main() { println!("Hello in English: {}", greetings::hello()); println!("Goodbye in English: {}", farewells::goodbye()); println!("Hello in Japanese: {}", japanese::hello()); println!("Goodbye in Japanese: {}", japanese::goodbye()); }
そして、 src/lib.rs
の japanese
モジュールをパブリックに変更します。
pub mod english; pub mod japanese;
続いて、2つの関数をパブリックにします。始めに src/japanese/greetings.rs
を、
pub fn hello() -> String { "こんにちは".to_string() }
そして src/japanese/farewells.rs
を、
pub fn goodbye() -> String { "さようなら".to_string() }
最後に、 src/japanese/mod.rs
を以下のように変更します。
pub use self::greetings::hello; pub use self::farewells::goodbye; mod greetings; mod farewells;
pub use
宣言は関数をモジュール階層 phrases::japanese
のスコープへ持ち込みます。japanese
モジュールの中で pub use
したため、 phrases::japanese::greetings::hello()
と phrases::japanese::farewells::goodbye()
にコードがあるのにも関わらず、 phrases::japanese::hello()
関数と phrases::japanese::goodbye()
関数が使えるようになります。内部の構成で外部向けのインターフェースが決まるわけではありません。
pub use
によって各関数を japanese
スコープの中に持ち込めるようになりました。 greetings
から現在のスコープへ全てをインクルードする代わりに、 pub use self::greetings::*
とすることでワイルドカード構文が使えます。
self
とはなんでしょう? ええっと、デフォルトでは、 use
宣言はクレートのルートから始まる絶対パスです。 self
は代わりに現在位置からの相対パスにします。 use
にはもう1つ特別な形式があり、現在位置から1つ上へのアクセスに use super::
が使えます。多くのシェルにおけるカレントディレクトリと親ディレクトリの表示になぞらえ、 .
が self
で、 ..
が super
であるという考え方を好む人もそれなりにいます。
use
でなければ、パスは相対です。foo::bar()
は私達のいる場所から相対的に foo
の内側の関数を参照します。 ::foo::bar()
のように ::
から始まるのであれば、クレートのルートからの絶対パスで、先程とは異なる foo
を参照します。
これはビルドして実行できます。
$ cargo run
Compiling phrases v0.0.1 (file:///home/you/projects/phrases)
Running `target/debug/phrases`
Hello in English: Hello!
Goodbye in English: Goodbye.
Hello in Japanese: こんにちは
Goodbye in Japanese: さようなら
extern crate
及び use
文に対し、Rustは簡潔さと利便性を付加できる上級者向けオプションを幾つか提供しています。以下が例になります。
extern crate phrases as sayings; use sayings::japanese::greetings as ja_greetings; use sayings::japanese::farewells::*; use sayings::english::{self, greetings as en_greetings, farewells as en_farewells}; fn main() { println!("Hello in English; {}", en_greetings::hello()); println!("And in Japanese: {}", ja_greetings::hello()); println!("Goodbye in English: {}", english::farewells::goodbye()); println!("Again: {}", en_farewells::goodbye()); println!("And in Japanese: {}", goodbye()); }
何が起きているでしょう?
第一に、インポートされているものを extern crate
と use
双方でリネームしています。そのため 「phrases」という名前のクレートであっても、ここでは「sayings」として参照することになります。同様に、始めの use
文はクレートから japanese::greetings
を引き出していますが、単純な greetings
ではなく ja_greetings
で利用できるようにしています。これは異なる場所から同じ名前のアイテムをインポートする際、曖昧さを回避するのに役立ちます。
第二の use
文では sayings::japanese::farewells
モジュールから 全ての シンボルを持ってくるためにスターグロブを使っています。ご覧の通り、最後にモジュールの修飾無しで日本語の goodbye
関数を参照できています。なお、この類のグロブは慎重に使うべきです。
第三の use
文はもっと詳しい説明が必要です。これは3つの use
文を1つに圧縮するグロブ「中括弧展開」を用いています(以前にLinuxのシェルスクリプトを書いたことがあるなら、この類の構文は慣れていることでしょう)。この文を展開した形式は以下のようになります。
use sayings::english; use sayings::english::greetings as en_greetings; use sayings::english::farewells as en_farewells;
ご覧の通り、波括弧は同一パス下にある幾つかのアイテムに対する use
文を圧縮します。また、この文脈における self
はパスの1つ手前を参照するだけです。(訳注: sayings::english::{self}
の self
が指す1つ手前は sayings::english
です)注意: 波括弧はネストできず、スターグロブと混ぜるとこともできません。