mcpField
R
rust-module-layout

rust-module-layout

github.com/full-stack-skills開発12115 回インストール5 週間前に更新
リポジトリ

rust-module-layout について

単一のRustクレート内で、モジュール、ファイル、可視性、公開再エクスポートを整理するスキルです。Rust開発者向けに、現代的な配置、APIファサード設計、よくあるアンチパターンのリファクタリングを解説します。

主な機能

  1. 01モジュール階層をソースツリーに対応
  2. 02lib.rsとmain.rsを薄いインデックスに維持
  3. 03現代的または旧式のファイル配置を選択
  4. 04Rustの可視性を適切に設定
  5. 05明示的な公開再エクスポートのファサード設計
  6. 06巨大なファイルとglob再エクスポートのリファクタリング

活用シーン

  1. 01新しいクレートのソース配置の計画
  2. 02大きなRustソースファイルの分割
  3. 03過度に広いクレートAPIの縮小
  4. 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.yaml
examples/
README.md
refactoring-anti-patterns.md
splitting-files.md
templates.md
visibility-decisions.md
examples/golden-facade/
Cargo.lock
Cargo.toml
examples/golden-facade/src/
ast.rs
error.rs
lib.rs
parser.rs
examples/golden-facade/src/ast/
print.rs
examples/golden-facade/src/parser/
grammar.rs
lexer.rs
references/
coming-from-java-python.md
facade-design.md
large-crate-layout.md
modernizing-mod-rs.md
module-resolution.md
naming.md
README.md
refactoring-flat-lib-rs.md
visibility-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 宣言、可視性、公開する再エクスポートの範囲を決定します。

対応範囲

✅ 得意なこと
  1. 論理的なモジュール階層を、物理的な src/ ディレクトリツリーに落とし込む
  2. lib.rs / main.rs を、ごみ捨て場ではなく薄いインデックスとして記述する
  3. foo.rs / foo/mod.rs / foo/ のいずれを使うか選択する(Edition 2018 以降とレガシー形式)
  4. モジュールに意味のある名前を付ける(db ではなく connection、rt ではなく runtime)
  5. pub / pub(crate) / pub(super) / pub(in path) を意図的に適用する
  6. 対象を絞った pub use によって、深いプライベートツリーの上にフラットな公開ファサードを設計する
  7. **「フラットな lib.rs + pub use *::*」**というアンチパターン(Java/Python から移植されたクレートによく見られる)を認識し、リファクタリングする
  8. 呼び出し側を壊さずに、500 行を超えるファイルをディレクトリに分割する
  9. レガシーな mod.rs レイアウトを、最新の foo.rs + foo/ 形式に移行する
⚠️ 前提条件
  1. Rust の所有権と基本的なモジュール構文 — rust-stable スキルを参照してください
  2. クレートとパッケージ、ワークスペースの違い — rust-workspace スキルを参照してください
❌ 対象外
  1. Cargo.toml / 依存関係 / フィーチャー → rust-cargo-build を使用してください
  2. クレートをワークスペースメンバーに分割すること → rust-workspace を使用してください
  3. 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.rs2018 より前のコードベース。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 再エクスポートは「そのまま動く」ため魅力的です。名前を入力すれば解決します。しかし、実際には次のようなコストがあります。

  1. 由来が失われます:my_crate::Pool — Pool は実際にはどこにあるのでしょうか? pool::? connection::pool::? connection::pool::v2::? 読者にも IDE にも分かりません。
  2. シンボルの競合が気付かれないまま発生します:異なるサブモジュールからの 2 つの pub use *::* が衝突し、後から書かれたものが優先されます。
  3. リファクタリングが見えにくくなります:Pool を pool:: から pool::v2:: に移動しても、再エクスポートが glob であるため差分に現れません。
  4. IDE の補完がノイズだらけになります:my_crate:: と入力すると、厳選された十数個ではなく、何百ものシンボルが表示されます。

アンチパターン(およびリファクタリング方法)

これらのパターンは、言語の慣習が異なる Java/Python 出身の開発者が作成したクレートによく見られます。以下の例はすべて匿名化していますが、実際に公開されているクレートをもとにしています。

#アンチパターン症状修正方法
1lib.rs がフラットで、あらゆる場所で glob 再エクスポートしているモジュールごとに pub mod foo; pub use foo::*; が繰り返されるpub use foo::*; を対象を絞った pub use foo::{A, B}; に置き換え、実装モジュールを非公開にします(mod foo;)
2src/ 直下に巨大なファイルがある複数の関心事が混在する 500 行以上のファイルが 1 つあるディレクトリに分割し、関心事ごとにファイルを 1 つ作成します(examples/splitting-files.md を参照)
3曖昧な/省略形のモジュール名db、io、rt、ext、util、common、core、typesconnection、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;)
6Edition 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 を参照)

各アンチパターンの詳しい手順付きリファクタリングについては、以下を参照してください。


肥大化したファイルの分割

ファイルが約 500 行を超えた場合や、複数の関心事が混在し始めた場合は、ディレクトリに分割します。

機械的な手順(呼び出し元の動作は変わりません):

  1. src/foo/ ディレクトリを作成します
  2. 元の foo.rs にある関心事ごとに src/foo/<concern>.rs を作成し、項目を移動します
  3. src/foo.rs を pub mod <concern>; の宣言と、旧 API を維持するための対象を絞った再エクスポートに置き換えます
  4. 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
}

ワークフロー

  1. 論理ドメインを特定する — クレートが扱う関心事(パース、AST、コード生成、ランタイム)を列挙します。それぞれがトップレベルモジュールになります。
  2. トップレベルの公開範囲を決める — 各モジュールについて、公開 API の一部(ユーザーがこれらの型を構築する)か、実装の詳細かを確認します。実装モジュールには mod(非公開)を指定します。
  3. ファサードを設計する — ユーザーが読むのはこのファイルだけだと想定して lib.rs を作成します。ユーザーが必要とするすべての pub use を列挙します。項目が約30を超える場合、クレートの役割が多すぎます。ワークスペースへの分割を検討してください。
  4. ディレクトリツリーを構成する — 各トップレベルモジュールについて、単一ファイル(テンプレートA)にするか、ディレクトリ(テンプレートB/C)にするかを決めます。新しいコードでは、最新の foo.rs + foo/ レイアウトを使用します。
  5. 意味の分かるモジュール名を付ける — 省略形を使わず、完全な単語を使います。ファイルを開かなくても中身が分かる名前にします。
  6. 公開範囲は必要最小限にする — まずすべてを非公開にします。呼び出し元が実際に必要とする場合に限り、pub(crate)、次に pub(super)、最後に pub へと公開範囲を広げます。
  7. 検証する — cargo check を実行し、次に cargo doc --no-deps --open を実行します。クレートのルートに生成される index.html が公開 API です。項目が100を超えている場合、ファサードから内部実装が漏れています。

注意点

  1. 公開範囲は親に束縛されます。 非公開の mod 内にある pub 項目は、外部からアクセスできません。「外部ユーザーから型が見えないのはなぜか」という問題の多くは、これが原因です。
  2. 同じ foo に対して foo.rs と foo/mod.rs を両方置くことはできません。コンパイラーエラーになります。どちらか一方を選んでください。
  3. **#[path] はファイルシステムの規約を崩すため、**生成コードや特殊なレイアウトでのみ使用してください。使用する場合は理由を記載してください。
  4. クレートのルートにある pub use foo::* は便利に見えますが、リファクタリングの差分が読みにくくなり、IDE のコード補完にもノイズが増えます。pub use foo::{A, B}; のように対象を絞った pub use を推奨します。
  5. extern crate foo; は、Edition 2018 以降のほとんどのクレートでは不要です。use foo as bar; を使ってください。extern crate は、#[macro_use] が必要なクレート(まれです)や、インポート時に名前を変更する場合に限って使用します。
  6. pub use crate::foo::Bar;、pub use self::foo::Bar;、pub use foo::Bar; はすべて有効です。現在のクレート内で絶対パスを記述する場合は crate:: が最も読みやすく、相対パスには self:: を使います。接頭辞のない foo::Bar が使えるのは、foo が外部クレートであるか、use のスコープ内にある場合だけです。
  7. pub モジュールの名前を変更すると、破壊的変更になります。メジャーバージョンを上げるか、非推奨のエイリアスを用意してください。例:pub mod old_name { pub use crate::new_name::*; }。
  8. mod.rs は非推奨ではありません。 Edition 2015 では必須であり、2018 以降でも引き続き使用できます。新しいコードでは最新の foo.rs + foo/ レイアウトが推奨されますが、mod.rs も間違いではありません。

オンデマンドリソース

公式リファレンス

コメント