Tomoki Ota's Blog

article icon

【Rust】モジュールシステム

作成日 

Rustのモジュールシステム

Rustではパブリック関数には pub をつけます。

greet.rs
pub fn hello() {
    println!("hello!");
}

pub をつけることで他のファイルでインポートすることが可能となります。

main.rs
mod greet;
fn main() {
  greet::hello();
}

これは以下のように1つで書いたものと同じです。

main.rs
fn main() {
    greet::hello();
}
 
mod greet {
    pub fn hello() {
        println!("Hello");
    }
}

これはgreetがサブモジュールであることを意味しています。 また、以下のように use を使うと省略してインポートすることが可能です。

main.rs
use greet::hello;
mod greet;
 
fn main() {
  hello();
}

サブモジュール

さて先ほどmainからgreetをインポートした際に、greetがサブモジュールとみなされていると説明しました。

さて、以下のように3つのファイルになったらどうなるでしょう。

greet.rs
pub fn hello() {
    println!("hello!");
}
user.rs
mod greet;
pub fn say() {
    greet::hello()
}
main.rs
mod user;
fn main() {
    user::say();
}

実行するとfile not found for module greet とコンパイルエラーとなります。 これは、userがmainのサブモジュール、greetがuserのサブモジュールとみなされているためです。わかりやすく1つのファイルで書いてみます。

main.rs
fn main() {
    user::say();
}
 
mod user {
    pub fn say() {
        greet::hello()
    }
    mod greet {
        pub fn hello() {
            println!("Hello");
        }
    }
}

ではどう修正すれば良いでしょうか?やり方は簡単に2つあります。

  1. 先ほどの1つのファイルのようにgreet.rsをuserのサブモジュールにしてあげる
  2. greetもmainのサブモジュールにしてあげる

サブモジュールを入れ子にする

main.rs
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.rs

greet.rsの場所が変わるだけで、中身は変わりません。

user/greet.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を使用した場合はプロジェクトのルートからの絶対パスとなります。

main.rs
fn main() {
    user::say();
}
 
mod user {
    pub fn say() {
        crate::greet::hello()
    }
}
 
mod greet {
    pub fn hello() {
        println!("Hello");
    }
}

これをファイルで分けて書くと、以下のようになります。

main.rs
mod greet;
mod user;
 
fn main() {
    user::say();
}
user.rs
pub fn say() {
    crate::greet::hello()
}
greet.rs
pub fn hello() {
    println!("Hello");
}

lib.rs

先ほどのサブモジュールを作成せずにimportするで、さらにモジュールが増えたらどうなるでしょうか。

mod foo;
mod foo2;
mod foo3;
// ・・・
 
fn main() {
    foo::hoge();
}

上記のようにmain.rsでのインポートが増えてしまいます。 そこでRustではlib.rsなるものを作成して、そこにインポートをまとめることが一般的となっております。

main.rs
fn main() {
    app::user::say();
}
greet.rs
pub fn hello() {
    println!("Hello");
}
user.rs
pub fn say() {
    crate::greet::hello()
}
lib.rs
mod greet;
pub mod user;

また、先ほどのmain.rsは以下のように書けます。

main.rs
use app::user::say;
fn main() {
    say();
}

また、useをmain.rsで使いたくない場合はlib.rsにuseを書くこともできます。

lib.rs
pub use crate::user::say;
mod greet;
mod user;

lib.rsにuseを書いた場合は、main.rsは以下のように変更します。

main.rs
fn main() {
    app::say();
}
この記事をシェアするx icon
アイコン画像
Tomoki Ota

フルスタックエンジニア。Goが好き。趣味はカメラと旅行です📷