rust-module-layout について
単一のRustクレート内で、モジュール、ファイル、可視性、公開再エクスポートを整理するスキルです。Rust開発者向けに、現代的な配置、APIファサード設計、よくあるアンチパターンのリファクタリングを解説します。
主な機能
- 01モジュール階層をソースツリーに対応
- 02lib.rsとmain.rsを薄いインデックスに維持
- 03現代的または旧式のファイル配置を選択
- 04Rustの可視性を適切に設定
- 05明示的な公開再エクスポートのファサード設計
- 06巨大なファイルとglob再エクスポートのリファクタリング
活用シーン
- 01新しいクレートのソース配置の計画
- 02大きなRustソースファイルの分割
- 03過度に広いクレートAPIの縮小
- 04旧式のmod.rs配置の更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ full-stack-skills/ rust-skills --skill rust-module-layoutインストール後、Claude Code で/rust-module-layoutを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/README.mdrefactoring-anti-patterns.mdsplitting-files.mdtemplates.mdvisibility-decisions.mdexamples/golden-facade/Cargo.lockCargo.tomlexamples/golden-facade/src/ast.rserror.rslib.rsparser.rsexamples/golden-facade/src/ast/print.rsexamples/golden-facade/src/parser/grammar.rslexer.rsreferences/coming-from-java-python.mdfacade-design.mdlarge-crate-layout.mdmodernizing-mod-rs.mdmodule-resolution.mdnaming.mdREADME.mdrefactoring-flat-lib-rs.mdvisibility-and-privacy.md詳細
Rust モジュールのレイアウト(単一クレート内)
根拠:The Rust Book ch7、Rust Reference ch7、Rust API Guidelines — Organization(C-HIERARCHY、C-REEXPORT)、Rust Style Guide。
このスキルは、rust-workspace に対するクレート内部向けのスキルです。前者のスキルでは、クレートの境界(ワークスペース、パッケージ、依存関係の方向)を決定します。このスキルでは、1 つのクレートの src/ 内に何を配置するか、つまりファイルツリー、mod 宣言、可視性、公開する再エクスポートの範囲を決定します。
対応範囲
✅ 得意なこと
- 論理的なモジュール階層を、物理的な
src/ディレクトリツリーに落とし込む lib.rs/main.rsを、ごみ捨て場ではなく薄いインデックスとして記述するfoo.rs/foo/mod.rs/foo/のいずれを使うか選択する(Edition 2018 以降とレガシー形式)- モジュールに意味のある名前を付ける(
dbではなくconnection、rtではなくruntime) pub/pub(crate)/pub(super)/pub(in path)を意図的に適用する- 対象を絞った
pub useによって、深いプライベートツリーの上にフラットな公開ファサードを設計する - **「フラットな lib.rs +
pub use *::*」**というアンチパターン(Java/Python から移植されたクレートによく見られる)を認識し、リファクタリングする - 呼び出し側を壊さずに、500 行を超えるファイルをディレクトリに分割する
- レガシーな
mod.rsレイアウトを、最新のfoo.rs + foo/形式に移行する
⚠️ 前提条件
- Rust の所有権と基本的なモジュール構文 —
rust-stableスキルを参照してください - クレートとパッケージ、ワークスペースの違い —
rust-workspaceスキルを参照してください
❌ 対象外
- Cargo.toml / 依存関係 / フィーチャー →
rust-cargo-buildを使用してください - クレートをワークスペースメンバーに分割すること →
rust-workspaceを使用してください unsafeブロックの可視性 →rust-unsafe-ffiを使用してください
データのプライバシー
このスキルはユーザーデータを収集、保存、送信しません。
破ってはならない 2 つのルール
ほかのことを忘れても、次のルールは覚えておいてください。存在するすべての Rust クレートがこれらに従います。
ルール 1 — ディレクトリは、宣言するまでモジュールではありません
// ❌ You created src/string/case.rs. The compiler does not see it.
// ✅ You must declare every level:
// src/lib.rs
pub mod string; // loads src/string.rs OR src/string/mod.rs
// src/string.rs (modern) OR src/string/mod.rs (legacy)
pub mod case; // loads src/string/case.rs
ディレクトリ内にあるファイルは、親モジュールで mod <name>; と宣言されるまでコンパイラーから見えません。ファイルシステムそのものがモジュールシステムである Java/Python/Go から来た開発者が、最も驚く点です。
ルール 2 — すべてはデフォルトで非公開であり、公開するのは pub だけです
// src/string/case.rs
pub fn to_snake_case(s: &str) -> &str { ... } // ✅ callable from outside
fn helper(s: &str) -> &str { ... } // 🔒 crate-internal to this file
| 修飾子 | 可視範囲 |
|---|---|
| (修飾子なし) | 現在のモジュールと、その子孫のみ |
pub(crate) | クレート全体。外部ユーザーには公開されません |
pub(super) | 親モジュールのみ |
pub(in path) | 祖先モジュール path と、その子孫 |
pub(self) | 修飾子なしと同じ(明示的に記述されることはほとんどありません) |
pub | すべてのユーザー(親モジュールの可視性に従います — 可視性は親に束縛されます) |
重要な注意点:項目に pub を付けても、外部からの可視性が保証されるわけではありません。クレートルートからその項目までの経路上にあるすべてのモジュールも pub でなければ、その項目には到達できません。そのため、mod string;(非公開)にすると、string::case::to_snake_case が pub であっても、string/ 以下のすべてが外部ユーザーから見えなくなります。
判断基准:foo.rs と foo/mod.rs と foo.rs + foo/
Rust 2018 以降では、mod.rs を使わない第 3 のレイアウトが導入されました。どれも有効なため、1 つを選び、クレート内で一貫させてください。
| レイアウト | ファイル構成 | 使用する場面 |
|---|---|---|
| 単一ファイル | src/foo.rs | モジュールの本体が 1 つのファイルに収まる場合(目安:300~500 行未満) |
| 従来のディレクトリ | src/foo/mod.rs + src/foo/bar.rs | 2018 より前のコードベース。Edition 2015 を対象とする場合に必須 |
| モダンなディレクトリ(2018 以降) | src/foo.rs + src/foo/bar.rs | 新規コードのデフォルト — ディレクトリごとにファイルが 1 つ少なく、mod.rs の定型コードも不要 |
// Modern layout (default for new crates)
src/
├── lib.rs // pub mod foo;
├── foo.rs // pub mod bar; pub fn root_fn() {}
└── foo/
└── bar.rs // pub fn bar_fn() {}
// Legacy layout (only if Edition 2015)
src/
├── lib.rs // pub mod foo;
└── foo/
├── mod.rs // pub mod bar;
└── bar.rs
混在も可能ですが、推奨されません。 あるサブツリーで foo/mod.rs を使い、別のサブツリーで bar.rs + bar/ を使うと、読者はディレクトリごとに確認する必要があります。クレートごとに 1 つを選んでください(バリデータースクリプトが一貫性をチェックします)。
標準的なレイアウトテンプレート
テンプレート A — 小規模なクレート(約 1000 LOC 未満、単一ドメイン)
src/
├── lib.rs // 5–30 lines: mod decls + pub use facade
├── error.rs // crate error type
├── parser.rs // one file per major concern
├── ast.rs
└── codegen.rs
// src/lib.rs
//! One-line crate docs.
mod error;
mod parser;
mod ast;
mod codegen;
pub use error::Error;
pub use parser::parse; // targeted re-export — only the most-used items
pub use ast::{Expr, Stmt};
テンプレート B — 中規模なクレート(約 1k~10k LOC、複数のサブドメイン)
src/
├── lib.rs // index + facade
├── error.rs
├── parser/
│ ├── mod.rs // (or parser.rs in modern layout) — declares submodules
│ ├── lexer.rs
│ ├── grammar.rs
│ └── error.rs // parser-specific errors, re-exported upward
├── ast/
│ ├── mod.rs
│ ├── expr.rs
│ ├── stmt.rs
│ └── visit.rs
└── codegen/
├── mod.rs
├── llvm.rs
└── cranelift.rs
// src/lib.rs
mod error;
mod parser;
mod ast;
mod codegen;
pub use error::Error;
pub use parser::Parser; // types users construct
pub use ast::{Expr, Stmt, Module}; // data types users manipulate
// Note: codegen stays internal — users don't construct it directly
テンプレート C — 大規模なクレート(10k LOC 以上、深いネスト)
テンプレート B と同じですが、次の点が異なります。
- 各トップレベルディレクトリは、3~4 階層までネストできます(
parser/grammar/expr/primary.rs) lib.rsは薄い状態を保ちます(最大約 50 行) —modと選択したpub useのみ- サブディレクトリの
mod.rsファイルはサブファサードとして機能します。最も便利な項目を 1 階層上に再エクスポートしつつ、実装の詳細は非公開にします - 実例となる 50 ファイルの例については、
references/large-crate-layout.mdを参照してください
パブリックファサードパターン(C-REEXPORT)
Rust API Guidelines(C-REEXPORT)では、ユーザーが use my_crate::deep::path::Thing ではなく use my_crate::Thing と書けるよう、クレートルートからよく使われる型を再エクスポートすることが推奨されています。
// src/lib.rs — GOOD
mod connection;
mod pool;
mod query;
mod error;
// Targeted re-exports — only the items users actually need
pub use connection::Connection;
pub use pool::Pool;
pub use query::{Query, QueryBuilder};
pub use error::Error;
// The internal tree (connection/, pool/, query/) stays private.
// Users cannot reach my_crate::connection::tcp::TcpStream even if they try.
対象を指定した再エクスポートと glob 再エクスポート
| 形式 | 適切な場合 | 避けるべき場合 |
|---|---|---|
pub use foo::{Bar, Baz}; | ✅ デフォルト — 明示的で IDE に親和性があり、項目ごとに作成者の判断が必要 | — |
pub use foo::*; | まれに — foo が、ルートに属する安定した名前の項目だけを含むリーフモジュールの場合に限る | ❌ ほとんどの場合 — 無関係なシンボルを数多くルート名前空間に流し込み、由来を見えにくくし、IDE の自動補完を損ないます |
すべての foo に対する pub use foo::*; | 決して使わない | ❌ これはフラットな lib.rs のアンチパターンです(次のセクションを参照) |
glob 再エクスポートは「そのまま動く」ため魅力的です。名前を入力すれば解決します。しかし、実際には次のようなコストがあります。
- 由来が失われます:
my_crate::Pool—Poolは実際にはどこにあるのでしょうか?pool::?connection::pool::?connection::pool::v2::? 読者にも IDE にも分かりません。 - シンボルの競合が気付かれないまま発生します:異なるサブモジュールからの 2 つの
pub use *::*が衝突し、後から書かれたものが優先されます。 - リファクタリングが見えにくくなります:
Poolをpool::からpool::v2::に移動しても、再エクスポートが glob であるため差分に現れません。 - IDE の補完がノイズだらけになります:
my_crate::と入力すると、厳選された十数個ではなく、何百ものシンボルが表示されます。
アンチパターン(およびリファクタリング方法)
これらのパターンは、言語の慣習が異なる Java/Python 出身の開発者が作成したクレートによく見られます。以下の例はすべて匿名化していますが、実際に公開されているクレートをもとにしています。
| # | アンチパターン | 症状 | 修正方法 |
|---|---|---|---|
| 1 | lib.rs がフラットで、あらゆる場所で glob 再エクスポートしている | モジュールごとに pub mod foo; pub use foo::*; が繰り返される | pub use foo::*; を対象を絞った pub use foo::{A, B}; に置き換え、実装モジュールを非公開にします(mod foo;) |
| 2 | src/ 直下に巨大なファイルがある | 複数の関心事が混在する 500 行以上のファイルが 1 つある | ディレクトリに分割し、関心事ごとにファイルを 1 つ作成します(examples/splitting-files.md を参照) |
| 3 | 曖昧な/省略形のモジュール名 | db、io、rt、ext、util、common、core、types | connection、buffer、runtime など、意味が明確な名前に変更します(references/naming.md を参照) |
| 4 | プライバシーの漏えい(あらゆる場所で pub を使用) | pub struct Pool { pub inner: Vec<_>, pub config: _, } | 既定では非公開にし、安定した場合にのみ公開範囲を広げます(references/visibility-and-privacy.md を参照) |
| 5 | 深くネストされた公開ツリー | my_crate::connection::tcp::stream::TcpStream | ツリーを非公開に保ち、フラットなファサードを公開します(mod connection; pub use connection::TcpStream;) |
| 6 | Edition 2018 以降のクレートで mod.rs を使用 | 同じクレート内で foo/mod.rs と bar.rs + bar/ の構成が混在している | クレートごとにどちらか一方を選び、モダンな foo.rs + foo/ を推奨します(references/modernizing-mod-rs.md を参照) |
| 7 | #[macro_use] mod foo; | 2018 より前のレガシーなマクロインポートにより、すべてのマクロがクレート全体に漏れる | #[macro_export] と明示的な pub use を使用します(examples/refactoring-anti-patterns.md の Refactor 5 を参照) |
各アンチパターンの詳しい手順付きリファクタリングについては、以下を参照してください。
examples/refactoring-anti-patterns.md— 修正前/修正後のコードを含む、7 つの詳細な解説references/refactoring-flat-lib-rs.md— インデックスと glob を使ったlib.rsを、整理されたファサードへ完全に移行する方法
肥大化したファイルの分割
ファイルが約 500 行を超えた場合や、複数の関心事が混在し始めた場合は、ディレクトリに分割します。
機械的な手順(呼び出し元の動作は変わりません):
src/foo/ディレクトリを作成します- 元の
foo.rsにある関心事ごとにsrc/foo/<concern>.rsを作成し、項目を移動します src/foo.rsをpub mod <concern>;の宣言と、旧 API を維持するための対象を絞った再エクスポートに置き換えますcargo checkを実行します — 呼び出し元を一切変更せずにコンパイルが成功するはずです
600 行のファイルをディレクトリに分割する詳しい例については、examples/splitting-files.md を参照してください。
ディレクトリ分割とワークスペース分割
- ディレクトリに分割する(同じクレート内):サブドメインが型を共有する、一緒に使われる、または密接に結合している場合です。バージョンは 1 つで、公開も 1 回です。
- ワークスペースに分割する(別々のクレート):サブドメインを独立して利用できる場合、安定性の変化の軌跡が異なる場合、または依存関係の範囲が異なる場合です。
ワークスペース単位での判断については、rust-workspace を参照してください。
可視性の早見表
// Public — anyone with a path to this item can use it
pub fn f() {}
// Crate-visible — usable anywhere inside this crate, not by external users
pub(crate) fn g() {}
// Parent-visible — usable in the parent module and its descendants
pub(super) fn h() {}
// Restricted to an ancestor path and its descendants
pub(in crate::foo::bar) fn i() {}
// Module-private (default) — usable only inside this module
fn j() {}
// `pub` with a private parent is effectively `pub(crate)` from the outside.
// Privacy is **parent-bound**: external reachability requires every module
// on the path from the crate root to be `pub`.
mod internal {
pub struct Hidden; // pub, but `internal` is private → unreachable externally
}
ワークフロー
- 論理ドメインを特定する — クレートが扱う関心事(パース、AST、コード生成、ランタイム)を列挙します。それぞれがトップレベルモジュールになります。
- トップレベルの公開範囲を決める — 各モジュールについて、公開 API の一部(ユーザーがこれらの型を構築する)か、実装の詳細かを確認します。実装モジュールには
mod(非公開)を指定します。 - ファサードを設計する — ユーザーが読むのはこのファイルだけだと想定して
lib.rsを作成します。ユーザーが必要とするすべてのpub useを列挙します。項目が約30を超える場合、クレートの役割が多すぎます。ワークスペースへの分割を検討してください。 - ディレクトリツリーを構成する — 各トップレベルモジュールについて、単一ファイル(テンプレートA)にするか、ディレクトリ(テンプレートB/C)にするかを決めます。新しいコードでは、最新の
foo.rs + foo/レイアウトを使用します。 - 意味の分かるモジュール名を付ける — 省略形を使わず、完全な単語を使います。ファイルを開かなくても中身が分かる名前にします。
- 公開範囲は必要最小限にする — まずすべてを非公開にします。呼び出し元が実際に必要とする場合に限り、
pub(crate)、次にpub(super)、最後にpubへと公開範囲を広げます。 - 検証する —
cargo checkを実行し、次にcargo doc --no-deps --openを実行します。クレートのルートに生成されるindex.htmlが公開 API です。項目が100を超えている場合、ファサードから内部実装が漏れています。
注意点
- 公開範囲は親に束縛されます。 非公開の
mod内にあるpub項目は、外部からアクセスできません。「外部ユーザーから型が見えないのはなぜか」という問題の多くは、これが原因です。 - 同じ
fooに対してfoo.rsとfoo/mod.rsを両方置くことはできません。コンパイラーエラーになります。どちらか一方を選んでください。 - **
#[path]はファイルシステムの規約を崩すため、**生成コードや特殊なレイアウトでのみ使用してください。使用する場合は理由を記載してください。 - クレートのルートにある
pub use foo::*は便利に見えますが、リファクタリングの差分が読みにくくなり、IDE のコード補完にもノイズが増えます。pub use foo::{A, B};のように対象を絞ったpub useを推奨します。 extern crate foo;は、Edition 2018 以降のほとんどのクレートでは不要です。use foo as bar;を使ってください。extern crateは、#[macro_use]が必要なクレート(まれです)や、インポート時に名前を変更する場合に限って使用します。pub use crate::foo::Bar;、pub use self::foo::Bar;、pub use foo::Bar;はすべて有効です。現在のクレート内で絶対パスを記述する場合はcrate::が最も読みやすく、相対パスにはself::を使います。接頭辞のないfoo::Barが使えるのは、fooが外部クレートであるか、useのスコープ内にある場合だけです。pubモジュールの名前を変更すると、破壊的変更になります。メジャーバージョンを上げるか、非推奨のエイリアスを用意してください。例:pub mod old_name { pub use crate::new_name::*; }。mod.rsは非推奨ではありません。 Edition 2015 では必須であり、2018 以降でも引き続き使用できます。新しいコードでは最新のfoo.rs + foo/レイアウトが推奨されますが、mod.rsも間違いではありません。
オンデマンドリソース
- コピーして使えるひな形付きのレイアウトテンプレート
- フラットな lib.rs のリファクタリング手順
- 大規模クレートのレイアウト(50ファイルの実例)
foo/mod.rsからfoo.rs + foo/への移行- Java/Python から Rust へのモジュールの考え方
- 公開ファサードの設計(C-REEXPORT)
- 命名規則