モジュールシステム
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 のモジュールシステムの設計原則:
- デフォルト非公開: すべてのアイテムはデフォルトで非公開.
pubで明示的に公開する - 段階的な公開レベル:
pub,pub(crate),pub(super),pub(in path)による細やかな制御 - ファイルシステムとの対応: モジュール構造がディレクトリ構造と対応し,コードの物理的な配置が論理的な構造と一致する
- 柔軟な use 構文: ネストしたインポート,エイリアス,ローカルスコープでの use
- re-export:
pub useによる API の表面と内部構造の分離
この設計は「内部実装を隠蔽し,公開 API を意図的に制御する」というソフトウェアエンジニアリングの原則を,言語レベルで強制する.大規模なプロジェクトにおいて,モジュール境界が自然に API 設計の境界となり,保守性とリファクタリングの容易さを支える基盤となっている.