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_a1つで所帯を持っているので子である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ファイルなんかも存在するので参考にどうぞ。