Wonderfull Rust

モジュールシステム

Rust のモジュールシステム

Rust のモジュールシステムは,コードの整理,名前空間の管理,可視性の制御を統一的に扱う仕組みだ.他の言語のモジュールシステムと比較して,デフォルト非公開 (private by default)明示的な公開レベル制御 が特徴的だ.

モジュールの基本

mod キーワード

モジュールは mod キーワードで定義する:

// 同一ファイル内でモジュールを定義
mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    fn internal_helper() -> i32 {
        // 非公開:このモジュールの外からはアクセスできない
        42
    }
}

fn main() {
    let result = math::add(1, 2);
    // math::internal_helper();  // コンパイルエラー: 非公開
}

ファイルシステムとモジュール

Rust のモジュールシステムは,ファイルシステムのディレクトリ構造と対応する:

src/
├── main.rs       (クレートルート)
├── math.rs       (math モジュール)
└── network/
    ├── mod.rs    (network モジュール)
    └── http.rs   (network::http サブモジュール)
// src/main.rs
mod math;      // src/math.rs を読み込む
mod network;   // src/network/mod.rs を読み込む

fn main() {
    math::add(1, 2);
    network::http::get("https://example.com");
}

Rust 2018 edition 以降は,mod.rs の代わりにファイル名でモジュールを表現することもできる:

src/
├── main.rs
├── math.rs
└── network.rs      (network モジュール)
└── network/
    └── http.rs     (network::http サブモジュール)

デフォルト非公開

Rust のモジュールシステムの最も重要な設計判断は,すべてのアイテムがデフォルトで非公開 (private) である点だ.

mod my_module {
    struct InternalData {
        value: i32,
    }

    fn internal_function() -> i32 {
        42
    }

    pub fn public_function() -> i32 {
        // 同じモジュール内からはアクセス可能
        internal_function()
    }
}

Java ではデフォルトが package-private,Python では慣習的にアンダースコアプレフィックスで非公開を示すが強制力はない,JavaScript (ESM) ではデフォルトが module-private だが export で公開する.

Rust のアプローチは「明示的に pub をつけたものだけが公開される」という,最小権限の原則に忠実な設計だ.これにより:

  • 内部実装の変更が API 互換性を壊さない: pub でないものは外部から使われていないことが保証される
  • 意図しない公開を防ぐ: うっかり内部関数を公開してしまうリスクがない
  • コードを読む際の手がかり: pub がついているかどうかで,そのアイテムが外部向けの API なのか内部実装なのかが即座にわかる

公開レベルの細かい制御

Rust は pub に修飾子をつけることで,公開範囲を細かく制御できる:

mod outer {
    pub mod inner {
        // どこからでもアクセス可能
        pub fn fully_public() {}

        // 親モジュール (outer) からのみアクセス可能
        pub(super) fn parent_only() {}

        // 同じクレート内からのみアクセス可能
        pub(crate) fn crate_only() {}

        // 特定のパスからのみアクセス可能
        pub(in crate::outer) fn specific_path() {}

        // デフォルト: このモジュール内からのみ
        fn private() {}
    }

    fn test() {
        inner::fully_public();    // OK
        inner::parent_only();     // OK: 親モジュールだから
        inner::crate_only();      // OK: 同じクレート内だから
        inner::specific_path();   // OK: crate::outer だから
        // inner::private();      // NG: 非公開
    }
}
可視性 アクセス可能な範囲
pub どこからでも(外部クレートを含む)
pub(crate) 同じクレート内
pub(super) 親モジュール
pub(in path) 指定されたパス以下
(なし) 同じモジュール内のみ

この段階的な可視性制御は,大規模なプロジェクトにおいて特に有用だ.「クレート外部には公開しないが,クレート内部では自由に使えるようにしたい」という場面で pub(crate) を使い,「サブモジュールの実装詳細だが,親モジュールからは使いたい」という場面で pub(super) を使う.

use: パスの簡略化

use キーワードは,モジュールパスを現在のスコープに導入する:

// フルパスでのアクセス
std::collections::HashMap::new();

// use で導入
use std::collections::HashMap;
HashMap::new();

use の柔軟な構文

// 複数のアイテムを一度に導入
use std::collections::{HashMap, HashSet, BTreeMap};

// モジュール自体を導入
use std::collections;
let map = collections::HashMap::new();

// ネストしたパスの統合
use std::io::{self, Read, Write};
// ↑ これは以下と同等:
// use std::io;
// use std::io::Read;
// use std::io::Write;

// グロブインポート(テストでよく使われる)
use std::collections::*;

// エイリアス
use std::collections::HashMap as Map;

use の配置: トップレベルとローカル

use はファイルの先頭だけでなく,関数やブロック内にも書ける:

fn process() {
    use std::collections::HashMap;  // この関数内でのみ有効

    let mut map = HashMap::new();
    map.insert("key", "value");
}

この柔軟性により,名前空間の汚染を最小限に抑えつつ,必要な場所で必要なアイテムだけを導入できる.

慣習的な use のスタイル

Rust コミュニティには use の書き方に関する慣習がある:

// 1. 標準ライブラリ
use std::collections::HashMap;
use std::io::{self, Write};

// 2. 外部クレート
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;

// 3. 自クレート内のモジュール
use crate::config::Settings;
use crate::models::User;

cargo fmt がこの順序を自動的に整理してくれる.

パスの起点

Rust のモジュールパスには 3 つの起点がある:

mod a {
    pub mod b {
        pub fn func() {}
    }

    pub fn example() {
        // 絶対パス: クレートルートから
        crate::a::b::func();

        // 相対パス: 現在のモジュールから
        b::func();

        // self: 現在のモジュールを明示
        self::b::func();

        // super: 親モジュール
        // super::other_module::func();
    }
}
起点 意味
crate:: クレートルートからの絶対パス
self:: 現在のモジュール
super:: 親モジュール
(なし) 現在のスコープからの相対パス

re-export: pub use

pub use は,アイテムを再公開 (re-export) する:

// src/lib.rs
mod internal {
    pub mod deeply {
        pub mod nested {
            pub struct ImportantType;
        }
    }
}

// 内部構造を隠蔽しつつ,便利なパスで公開
pub use internal::deeply::nested::ImportantType;

// 外部からは my_crate::ImportantType でアクセスできる
// 内部のモジュール構造は見えない

pub use は公開 API の設計において重要なツールだ.内部のモジュール構造をリファクタリングしても,pub use のパスを維持する限り,外部向けの API は変わらない.

クレートとモジュールの関係

Rust のコンパイル単位は クレート (crate) だ:

  • バイナリクレート: fn main() を持つ実行可能なプログラム
  • ライブラリクレート: 他のクレートから利用されるライブラリ
my_project/
├── Cargo.toml
├── src/
│   ├── main.rs    (バイナリクレートのルート)
│   └── lib.rs     (ライブラリクレートのルート)

一つのパッケージ(Cargo.toml が定義する単位)は,一つのライブラリクレートと複数のバイナリクレートを含むことができる.

まとめ

Rust のモジュールシステムの設計原則:

  1. デフォルト非公開: すべてのアイテムはデフォルトで非公開.pub で明示的に公開する
  2. 段階的な公開レベル: pub, pub(crate), pub(super), pub(in path) による細やかな制御
  3. ファイルシステムとの対応: モジュール構造がディレクトリ構造と対応し,コードの物理的な配置が論理的な構造と一致する
  4. 柔軟な use 構文: ネストしたインポート,エイリアス,ローカルスコープでの use
  5. re-export: pub use による API の表面と内部構造の分離

この設計は「内部実装を隠蔽し,公開 API を意図的に制御する」というソフトウェアエンジニアリングの原則を,言語レベルで強制する.大規模なプロジェクトにおいて,モジュール境界が自然に API 設計の境界となり,保守性とリファクタリングの容易さを支える基盤となっている.

本に戻る