クレートとモジュール

プロジェクトが大きくなり始めた際に、コードを小さなまとまりに分割しそれらでプロジェクトを組み立てるのはソフトウェア工学における優れた慣例だと考えられています。幾つかの機能をプライベートとし、また幾つかをパブリックとできるように、はっきりと定義されたインターフェースも重要となります。こういった事柄を容易にするため、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 を以下のようにしましょう。

fn main() { mod english { mod greetings { } mod farewells { } } mod japanese { mod greetings { } mod farewells { } } }
mod english {
    mod greetings {
    }

    mod farewells {
    }
}

mod japanese {
    mod greetings {
    }

    mod farewells {
    }
}

mod キーワードの後、モジュールの名前を与えます。モジュール名はRustの他の識別子の慣例に従います。つまり lower_snake_case です。各モジュールの内容は波括弧( {} )の中に書きます。

与えられた mod 内で、サブ mod を定義することができます。サブモジュールは2重コロン( :: )記法で参照できます。ネストされた4つのモジュールは english::greetingsenglish::farewellsjapanese::greetings 、そして japanese::farewells です。これらサブモジュールは親モジュール配下の名前空間になるため、名前は競合しません。つまり english::greetingsjapanese::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 はクレートのルートで、以下のようになっています。

fn main() { mod english; mod japanese; }
mod english;
mod japanese;

これら2つの宣言はRustへ書き手の好みに合わせて src/english.rssrc/japanese.rs 、または src/english/mod.rssrc/japanese/mod.rs のどちらかを見よと伝えています。今回の場合、サブモジュールがあるため私たちは後者を選択しました。 src/english/mod.rssrc/japanese/mod.rs は両方とも以下のようになっています。

fn main() { mod greetings; mod farewells; }
mod greetings;
mod farewells;

繰り返すと、これら宣言はRustへ src/english/greetings.rssrc/japanese/greetings.rs 、または src/english/farewells/mod.rssrc/japanese/farewells/mod.rs のどちらかを見よと伝えています。これらサブモジュールは自身配下のサブモジュールを持たないため、私たちは src/english/greetings.rssrc/japanese/farewells.rs を選びました。ヒュー!

src/english/greetings.rssrc/japanese/farewells.rs の中身は現在両方とも空です。幾つか関数を追加しましょう。

src/english/greetings.rs に以下を入力します。

fn main() { fn hello() -> String { "Hello!".to_string() } }
fn hello() -> String {
    "Hello!".to_string()
}

src/english/farewells.rs に以下を入力します。

fn main() { fn goodbye() -> String { "Goodbye.".to_string() } }
fn goodbye() -> String {
    "Goodbye.".to_string()
}

src/japanese/greetings.rs に以下を入力します。

fn main() { fn hello() -> String { "こんにちは".to_string() } }
fn hello() -> String {
    "こんにちは".to_string()
}

勿論、このwebページからコピー&ペーストしたり、他の何かをタイプしても構いません。モジュールシステムを学ぶのに「konnichiwa」と入力するのは重要なことではありません。

src/japanese/farewells.rs に以下を入力します。

fn main() { fn goodbye() -> String { "さようなら".to_string() } }
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 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.rssrc/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()); }
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 を加えましょう。

fn main() { pub mod english; mod japanese; }
pub mod english;
mod japanese;

また src/english/mod.rs にて、両方とも pub にしましょう。

fn main() { pub mod greetings; pub mod farewells; }
pub mod greetings;
pub mod farewells;

src/english/greetings.rs にて、 fn の宣言に pub を加えましょう。

fn main() { pub fn hello() -> String { "Hello!".to_string() } }
pub fn hello() -> String {
    "Hello!".to_string()
}

また src/english/farewells.rs にもです。

fn main() { pub fn goodbye() -> String { "Goodbye.".to_string() } }
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.

pubstruct や そのメンバのフィールドにも使えます。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()); }
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()); }
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()); }
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()); }
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.rsjapanese モジュールをパブリックに変更します。

fn main() { pub mod english; pub mod japanese; }
pub mod english;
pub mod japanese;

続いて、2つの関数をパブリックにします。始めに src/japanese/greetings.rs を、

fn main() { pub fn hello() -> String { "こんにちは".to_string() } }
pub fn hello() -> String {
    "こんにちは".to_string()
}

そして src/japanese/farewells.rs を、

fn main() { pub fn goodbye() -> String { "さようなら".to_string() } }
pub fn goodbye() -> String {
    "さようなら".to_string()
}

最後に、 src/japanese/mod.rs を以下のように変更します。

fn main() { pub use self::greetings::hello; pub use self::farewells::goodbye; mod greetings; mod farewells; }
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 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 crateuse 双方でリネームしています。そのため 「phrases」という名前のクレートであっても、ここでは「sayings」として参照することになります。同様に、始めの use 文はクレートから japanese::greetings を引き出していますが、単純な greetings ではなく ja_greetings で利用できるようにしています。これは異なる場所から同じ名前のアイテムをインポートする際、曖昧さを回避するのに役立ちます。

第二の use 文では sayings::japanese::farewells モジュールから 全ての シンボルを持ってくるためにスターグロブを使っています。ご覧の通り、最後にモジュールの修飾無しで日本語の goodbye 関数を参照できています。なお、この類のグロブは慎重に使うべきです。

第三の use 文はもっと詳しい説明が必要です。これは3つの use 文を1つに圧縮するグロブ「中括弧展開」を用いています(以前にLinuxのシェルスクリプトを書いたことがあるなら、この類の構文は慣れていることでしょう)。この文を展開した形式は以下のようになります。

fn main() { use sayings::english; use sayings::english::greetings as en_greetings; use sayings::english::farewells as en_farewells; }
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 です)注意: 波括弧はネストできず、スターグロブと混ぜるとこともできません。