Rustのモジュールの使い方
κeenです。たまにRustのモジュールが分かりづらいとの声を聞くので解説しますね。
※ 2018版 も書きました。恐らくこちらの方が求める情報と思います。
とりあえずサンプルプロジェクトを作ります。
$ cargo new module
エラいのは lib.rs
と main.rs
モジュールの話をする前にクレートの話をしないといけません。 クレートはRustのプログラムの一番大きな単位です。 大きく分けてライブラリを作るlibクレートと実行可能ファイルを作るbinクレートがあります。
libクレートならばlib.rs
が、binクレートならばmain.rs
がルートにあたるファイルです。ここから辿れるモジュールが全てです。プロジェクトにファイルが存在していてもこれらから辿れなかったらコンパイルされません。
モジュールの作り方は3種類
- ファイルの中にインラインで作る
- 1ファイルで作る
- 1ディレクトリで作る
のやり方があります。 1.はそんなに使われなくて、説明のために1ファイルにまとめたい時や、可視性でアレコレする時のテクニックなんかに使われます。
2.と3.の境界はサブモジュールを持つかどうかです。
2.のファイルで作る時は
$ find src
src
src/lib.rs
src/new_module.rs
のように作っておいて、lib.rsの中を
mod new_module;
のようにmod
で参照してあげれば使えます。
3.のディレクトリの時もそんなに変わらなくて、lib.rs
は変わらず、ファイルを
$ find src
src
src/lib.rs
src/new_module
src/new_module/mod.rs
のようにしてあげます。mod.rs
の名前はこれでないといけません。丁度サブモジュールのnew_module
にとってのlib.rs
のような存在がmod.rs
な訳です。
兄弟モジュール同士の可視性
モジュールの身分
さて、次のように2つのモジュールを作ってみましょう。lib.rsにはmodule_a
のみmod
宣言しておきます。
$ find src
src
src/lib.rs
src/module_a.rs
src/module_b.rs
$ cat src/lib.rs
mod module_a;
これでmodule_a.rs
に
mod module_b;
と書くと
$ cargo build
Compiling module v0.1.0 (file:///home/kim/Rust/module)
error[E0432]: unresolved import `module_a`
--> src/lib.rs:1:5
|
1 | use module_a;
| ^^^^^^^^ no `module_a` in the root
error: aborting due to previous error
error: Could not compile `module`.
To learn more, run the command again with --verbose.
と怒られます。一番エラいのはlib.rs
ですから、身分の低い module_a
ではmod
宣言出来ません。
君の名は
同じ設定で、lib.rs
にmodule_a
とmodule_b
両方をmod
宣言しておきます。
$ find src
src
src/lib.rs
src/module_a.rs
src/module_b.rs
$ cat src/lib.rs
mod module_a;
mod module_b;
この時、module_b
がlib.rs
から見た時とmodule_a.rs
から見た時で名前が変わります。
lib.rsから見た時は、以下の3つが使えます。
use ::module_a; // 絶対パス
use module_a; // 省略絶対パス
use self::module_a; // 相対パス
self
というのは予約語で、ディレクトリでいうところの.
に相当します。
module_a.rsからは同じく3つが使えますが、相対パスが変わります。
use ::module_a; // 絶対パス
use module_a; // 省略絶対パス
use super::module_a; // 相対パス
super
も予約語で、..
に相当します。
ファイルが同じディレクトリにいるので分かりづらいですが、libが1つ上の階層で、その下にmodule_aとmodule_bがぶら下がってる感じですね。
おじさんと隠し子
次のように、サブモジュールを作ってみます。
$src
src/lib.rs
src/module_a
src/module_a/mod.rs
src/module_a/submodule.rs
src/module_b.rs
$ cat src/lib.rs
mod module_a;
mod module_b;
$ cat src/module_a/mod.rs
mod submodule;
さて、この時sumbmodule
から見たmodule_b
の名前はどうなるでしょうか。もうお分かりかと思いますが、以下の3つです。
use ::module_a; // 絶対パス
use module_a; // 省略絶対パス
use super::super::module_a; // 相対パス
じゃ、逆にmodule_b
から見たsubmodule
はどうなるでしょうか。実は、module_b
からsubmodule
は見えません(lib.rsからも見えません)。module_a
1つで所帯を持っているので子であるsubmodule
を外に出すかはmodule_a
の一存で決まります。今回は隠し子にしている訳ですね。いくら兄弟モジュールとはいえ家庭にまでは入り込めないのです。
子供をちゃんと公開するには
$ cat src/module_a/mod.rs
mod submodule;
となっていたモジュール宣言を
$ cat src/module_a/mod.rs
pub mod submodule;
としてあげればOKです。そうするとmodule_b
からも見えて、以下の3種類で参照出来ます。
use ::module_a::submodule;
use module_a::submodule;
use super::module_a::submodule;
木箱の外から
さて、今までは1クレート内での話でした。クレートの外から見るとどうなるでしょうか。
新たにmain.rs
を追加しましょう。先程 lib.rs
とmain.rs
がエラいと話しましたが、どちらも別々のクレートを作るのでmain.rs
は完全にクレートの外です。
こんな感じですね。
$ find src
src
src/lib.rs
src/main.rs
src/module_a
src/module_a/mod.rs
src/module_a/submodule.rs
src/module_b.rs
$ cat src/lib.rs
mod module_a;
mod module_b;
$ cat src/module_a/mod.rs
pub mod submodule;
まず、外部のクレートを参照するにはextern crate
を書く必要があります。あとまあ、binクレートなのでmain
関数も必要ですね。
$ cat src/main.rs
extern carte module;
fn main() {}
では、module_b
はどういう名前で見えるでしょうか。見えません。一番エラいlib.rs
がmodule_b
をpub
にしていないのでクレートの外からは見えなくなっています。見たければ、
$ cat src/lib.rs
mod module_a;
pub mod module_b;
のようにします。
さて、これでmain.rsからどのように見えるかというと。
use ::module::module_b;
use module::module_b;
use self::module::module_b;
のように気持的にルート直下にクレートがモジュールとして配置されたように見えます。
家出
さて、同じようにlib.rs
でmodule_a
をpub mod
すればmodule::module_a::submodule
も参照出来るようになります。ところで、例えばmodule_a
は外に出さずにsubmodule
だけを公開したい時にはどうしたらいいでしょうか。そんなケースあるのかと疑問に思うかもしれませんが、あります。同じクレートであってもpub
にしないとサブモジュールにアクセス出来ないことを思い出して下さい。そうすると
pub mod submodule;
pub mod internal_submodule_a;
pub mod internal_submodule_b;
みたいなケースが発生するのは想像出来ると思います。そういう時は、lib.rs
側でどうにかいじれます。
$ cat src/lib.rs
mod module_a;
pub mod module_b;
となっていると思いますが、
$ cat src/lib.rs
mod module_a;
pub mod module_b;
pub use module_a::submodule;
と、pub use
を加えてあげることでmodule_a
の下から出すことが出来ます。
これをmain.rs
から使う時は
use module::submodule;
のように名字のmodule_a
が取れます。
さらに踏み込めば、use
はas
を使ってリネーム出来るので
$ cat src/lib.rs
mod module_a;
pub mod module_b;
pub use module_a::submodule as module_c;
のようにリネームuse
してあげれば
use module::module_c;
のように参照することも出来ます。完全に籍を外れて名前の上では別人ですね。
実装する時の都合とAPIとして公開する時の都合が違うので公開用にいじれる作りになってるんですね。
lib.rsとmain.rs
上ではlib.rs
を使って説明しましたが、main.rs
でも同じことが出来ます。
さて、ここからはスタイルの話ですが、私がRustを書く時はmain.rs
の中にmod
を書くことはないです。必ずlib.rs
を作って、そこでライブラリとしてまとめてからmain.rs
で使います。「アプリケーションはアプリケーションを記述するための巨大なDSLとそれを使った小さな実装からなる」という思想ですね。明示的に境界を作ることで自然とAPIを設計出来るのでコードが整理しやすくなります。
まとめ
lib.rs
とmain.rs
が一番エラい。mod
で「モジュールがある」宣言pub mod
で加えて上位のモジュールにも公開self
とsuper
の予約語pub use
で改名
力尽きてテストのためのモジュールの話が出来なかったのでドキュメントを読んでみて下さい。
因みにこれのようにテクニックが詰まったlib.rs
ファイルなんかも存在するので参考にどうぞ。