Rustのモジュールシステム
Rustではパブリック関数には pub をつけます。
pub fn hello() {
println!("hello!");
}pub をつけることで他のファイルでインポートすることが可能となります。
mod greet;
fn main() {
greet::hello();
}これは以下のように1つで書いたものと同じです。
fn main() {
greet::hello();
}
mod greet {
pub fn hello() {
println!("Hello");
}
}これはgreetがサブモジュールであることを意味しています。 また、以下のように use を使うと省略してインポートすることが可能です。
use greet::hello;
mod greet;
fn main() {
hello();
}サブモジュール
さて先ほどmainからgreetをインポートした際に、greetがサブモジュールとみなされていると説明しました。
さて、以下のように3つのファイルになったらどうなるでしょう。
pub fn hello() {
println!("hello!");
}mod greet;
pub fn say() {
greet::hello()
}mod user;
fn main() {
user::say();
}実行するとfile not found for module greet とコンパイルエラーとなります。
これは、userがmainのサブモジュール、greetがuserのサブモジュールとみなされているためです。わかりやすく1つのファイルで書いてみます。
fn main() {
user::say();
}
mod user {
pub fn say() {
greet::hello()
}
mod greet {
pub fn hello() {
println!("Hello");
}
}
}ではどう修正すれば良いでしょうか?やり方は簡単に2つあります。
- 先ほどの1つのファイルのようにgreet.rsをuserのサブモジュールにしてあげる
- greetもmainのサブモジュールにしてあげる
サブモジュールを入れ子にする
fn main() {
user::say();
}
mod user {
pub fn say() {
greet::hello()
}
mod greet {
pub fn hello() {
println!("Hello");
}
}
}これのようにサブモジュールが入れ子になるようにしてディレクトリ構想を変更します。
.
├── main.rs
├── user
│ └── greet.rs // userのサブモジュール
└── user.rsgreet.rsの場所が変わるだけで、中身は変わりません。
pub fn hello() {
println!("hello!");
}また、これは以下のようにも書けます。
.
├── main.rs
├── user
│ └── greet
│ └── mod.rs
└── user.rsフロントのディレクトリベースルーティング(e.g. page router)とファイルベースルーティング(e.g. App router)の違いに少し似ています。Next.jsでは後者の方が推奨されていますが、Rustではディレクトリベースの方が2018 editionで導入されて推奨されています。ファイルベースは2015 editionとなります。
サブモジュールを作成せずにimportする
crate::もしくはsuper::を使用することでサブモジュールからサブモジュールを呼び出すことができます。superは直近の親からの相対パスで、crateを使用した場合はプロジェクトのルートからの絶対パスとなります。
fn main() {
user::say();
}
mod user {
pub fn say() {
crate::greet::hello()
}
}
mod greet {
pub fn hello() {
println!("Hello");
}
}これをファイルで分けて書くと、以下のようになります。
mod greet;
mod user;
fn main() {
user::say();
}pub fn say() {
crate::greet::hello()
}pub fn hello() {
println!("Hello");
}lib.rs
先ほどのサブモジュールを作成せずにimportするで、さらにモジュールが増えたらどうなるでしょうか。
mod foo;
mod foo2;
mod foo3;
// ・・・
fn main() {
foo::hoge();
}上記のようにmain.rsでのインポートが増えてしまいます。
そこでRustではlib.rsなるものを作成して、そこにインポートをまとめることが一般的となっております。
fn main() {
app::user::say();
}ここでappはCargo.tomlに書いているpackageのnameとなります。
pub fn hello() {
println!("Hello");
}pub fn say() {
crate::greet::hello()
}mod greet;
pub mod user;また、先ほどのmain.rsは以下のように書けます。
use app::user::say;
fn main() {
say();
}また、useをmain.rsで使いたくない場合はlib.rsにuseを書くこともできます。
pub use crate::user::say;
mod greet;
mod user;lib.rsにuseを書いた場合は、main.rsは以下のように変更します。
fn main() {
app::say();
}