MCPInstall

rust-module-layout

Design and enforce the internal layout of a single Rust crate — lib.rs/main.rs as a thin index, semantically named directory modules, mod declarations, pub visibility, and targeted re-exports. Use when users ask how to organize src/, write lib.rs, split a growing crate into subdirectories, choose between foo.rs and foo/mod.rs, name modules, avoid flat lib.rs "index plus glob re-export" anti-patterns, or migrate Java-style or Python-style flat projects to idiomatic Rust module trees.

Installs
115
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/full-stack-skills/rust-skills --skill rust-module-layout
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads rust-module-layout automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Rust Module Layout (Inside a Single Crate)

Authority: The Rust Book ch7, Rust Reference ch7, Rust API Guidelines — Organization (C-HIERARCHY, C-REEXPORT), Rust Style Guide.

This skill is the crate-internal counterpart to rust-workspace. That skill decides crate boundaries (workspace, packages, dependency direction). This skill decides what lives inside one crate's src/: the file tree, the mod declarations, the visibility, and the public re-export surface.

Capability Boundaries

✅ Strengths

  1. Translating a logical module hierarchy into a physical src/ directory tree
  2. Writing lib.rs / main.rs as a thin index, not a dumping ground
  3. Choosing between foo.rs / foo/mod.rs / foo/ (Edition 2018+ vs legacy)
  4. Naming modules semantically (connection rather than db, runtime rather than rt)
  5. Applying pub / pub(crate) / pub(super) / pub(in path) deliberately
  6. Designing a flat public facade over a deep private tree via targeted pub use
  7. Recognizing and refactoring the "flat lib.rs + pub use *::*" anti-pattern (common in crates ported from Java/Python)
  8. Splitting a 500+ line file into a directory without breaking callers
  9. Migrating legacy mod.rs layouts to the modern foo.rs + foo/ form

⚠️ Prerequisites

  1. Rust ownership and basic module syntax — see the rust-stable skill
  2. Crate vs package vs workspace — see the rust-workspace skill

❌ Out of Scope

  1. Cargo.toml / dependencies / features → use rust-cargo-build
  2. Splitting a crate into workspace members → use rust-workspace
  3. Visibility of unsafe blocks → use rust-unsafe-ffi

Data Privacy

This skill does not collect, store, or transmit any user data.


The Two Inviolable Rules

If you remember nothing else, remember these. Every Rust crate in existence obeys them:

Rule 1 — Directories are not modules until you declare them

// ❌ 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

A file sitting in a directory is invisible to the compiler until a parent module declares mod <name>;. This is the #1 surprise for developers coming from Java/Python/Go, where the filesystem is the module system.

Rule 2 — Everything is private by default; only pub exposes

// 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
ModifierVisible to
(no modifier)Current module and its descendants only
pub(crate)Entire crate, not external users
pub(super)Parent module only
pub(in path)Ancestor module path and its descendants
pub(self)Same as no modifier (rarely written explicitly)
pubEveryone (subject to parent module visibility — privacy is parent-bound)

Critical subtlety: pub on an item does not guarantee external visibility. The item is reachable only if every module on the path from the crate root to it is also pub. This is why mod string; (private) hides everything under string/ from external users, even if string::case::to_snake_case is pub.


Decision: foo.rs vs foo/mod.rs vs foo.rs + foo/

Rust 2018+ introduced a third layout that eliminates mod.rs. All three are legal; pick one and be consistent within a crate.

LayoutFile layoutWhen to use
Single filesrc/foo.rsModule body fits in one file (rule of thumb: <300–500 lines)
Legacy directorysrc/foo/mod.rs + src/foo/bar.rsPre-2018 codebases; required when targeting Edition 2015
Modern directory (2018+)src/foo.rs + src/foo/bar.rsDefault for new code — one fewer file per directory, no mod.rs boilerplate
// 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

Mixing is allowed but discouraged. If one subtree uses foo/mod.rs and another uses bar.rs + bar/, readers have to check each directory individually. Pick one per crate (the validator script enforces consistency).


Canonical Layout Templates

Template A — Small crate (< ~1000 LOC, single domain)

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};

Template B — Medium crate (~1k–10k LOC, multiple subdomains)

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

Template C — Large crate (10k+ LOC, deep nesting)

Same as Template B but:

  • Each top-level directory may nest 3–4 levels deep (parser/grammar/expr/primary.rs)
  • lib.rs stays thin (~50 lines max) — only mod + selected pub use
  • Subdirectory mod.rs files act as sub-facades: they re-export their most useful items upward one level, but keep implementation details private
  • See references/large-crate-layout.md for a worked 50-file example

The Public Facade Pattern (C-REEXPORT)

The Rust API Guidelines (C-REEXPORT) recommend: the crate root re-exports the most common types so users write use my_crate::Thing rather than use my_crate::deep::path::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.

Targeted re-export vs glob re-export

FormWhen OKWhen bad
pub use foo::{Bar, Baz};✅ Default — explicit, IDE-friendly, forces author to make a decision per item—
pub use foo::*;Rarely — only when foo is a leaf module of stably-named items that all belong at the root❌ Most cases — dumps dozens of unrelated symbols into the root namespace, hides provenance, breaks IDE autocomplete
pub use foo::*; for every fooNever❌ This is the flat lib.rs anti-pattern (see next section)

Glob re-export is seductive because it "just works" — type a name, it resolves. But it has real costs:

  1. Provenance is lost: my_crate::Pool — where does Pool actually live? pool::? connection::pool::? connection::pool::v2::? Readers and IDEs cannot tell.
  2. Symbol conflicts silently: two pub use *::* from different submodules clash; last-write-wins.
  3. Refactoring is opaque: moving Pool from pool:: to pool::v2:: doesn't show up in diffs because the re-export is glob.
  4. IDE completion becomes noise: typing my_crate:: shows hundreds of symbols instead of a curated dozen.

Anti-Patterns (and how to refactor them)

These patterns appear frequently in crates authored by developers coming from Java/Python, where the language has different conventions. All examples are anonymized but drawn from real public crates.

#Anti-patternSymptomFix
1Flat lib.rs + glob re-exports everywherepub mod foo; pub use foo::*; repeated for every moduleReplace pub use foo::*; with targeted pub use foo::{A, B};; make implementation modules private (mod foo;)
2Monster file at src/ rootOne file with 500+ lines mixing multiple concernsSplit into a directory, one file per concern (see examples/splitting-files.md)
3Vague / abbreviated module namesdb, io, rt, ext, util, common, core, typesRename to semantic names: connection, buffer, runtime (see references/naming.md)
4Leaky privacy (pub everywhere)pub struct Pool { pub inner: Vec<_>, pub config: _, }Default to private; widen only when stable (see references/visibility-and-privacy.md)
5Deep public treemy_crate::connection::tcp::stream::TcpStreamKeep the tree private; expose a flat facade (mod connection; pub use connection::TcpStream;)
6mod.rs in Edition 2018+ crateMixed foo/mod.rs + bar.rs + bar/ layouts in same cratePick one per crate; prefer modern foo.rs + foo/ (see references/modernizing-mod-rs.md)
7#[macro_use] mod foo;Legacy pre-2018 macro import leaks all macros crate-wideUse #[macro_export] + explicit pub use (see examples/refactoring-anti-patterns.md Refactor 5)

For full step-by-step refactors of each anti-pattern, see:

  • examples/refactoring-anti-patterns.md — seven worked walkthroughs with before/after code
  • references/refactoring-flat-lib-rs.md — complete migration of an index + glob lib.rs to a curated facade

Splitting a Growing File

When a file exceeds ~500 lines or starts mixing concerns, split it into a directory.

Mechanical procedure (zero behavior change for callers):

  1. Create src/foo/ directory
  2. For each concern in the original foo.rs, create src/foo/<concern>.rs and move the items
  3. Replace src/foo.rs with pub mod <concern>; declarations and targeted re-exports to preserve the old API
  4. Run cargo check — compilation should succeed with zero changes to callers

For a worked 600-line → directory example, see examples/splitting-files.md.

Directory vs workspace split

  • Split into a directory (within the same crate): subdomains share types, are always used together, or are tightly coupled. One version, one publish.
  • Split into a workspace (separate crates): subdomains are independently useful, have different stability trajectories, or have different dependency footprints.

See rust-workspace for the workspace-level decision.


Visibility Cheat Sheet

// 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
}

Workflow

  1. Identify logical domains — list the concerns the crate addresses (parsing, AST, codegen, runtime). Each becomes a top-level module.
  2. Decide top-level visibility — for each module, ask: is this part of the public API (users construct these types) or implementation detail? Mark implementation modules mod (private).
  3. Design the facade — write lib.rs as if it were the only file users read. List every pub use they will need. If the list exceeds ~30 items, the crate is doing too much — consider a workspace split.
  4. Lay out the directory tree — for each top-level module, decide: single file (Template A) or directory (Template B/C). Use modern foo.rs + foo/ layout for new code.
  5. Name modules semantically — full words, no abbreviations. The module name should tell the reader what's inside without opening the file.
  6. Apply privacy minimally — start with everything private. Widen to pub(crate), then pub(super), then pub only when a caller actually needs it.
  7. Validate — run cargo check, then cargo doc --no-deps --open. The generated index.html for your crate root is your public API. If it lists 100+ items, your facade is leaking.

Gotchas

  1. Privacy is parent-bound. A pub item inside a private mod is not externally reachable. Many "why can't external users see my type?" bugs trace to this.
  2. foo.rs and foo/mod.rs cannot both exist for the same foo — the compiler errors out. Choose one.
  3. #[path] breaks filesystem conventions and should be reserved for generated code or unusual layouts. Document why if you use it.
  4. pub use foo::* at the crate root looks convenient but makes refactor diffs unreadable and IDE completion noisy. Prefer targeted pub use foo::{A, B};.
  5. extern crate foo; is unnecessary in Edition 2018+ for most crates; just use foo as bar;. Keep extern crate only for crates that need #[macro_use] (rare) or rename-on-import.
  6. pub use crate::foo::Bar; vs pub use self::foo::Bar; vs pub use foo::Bar; — all legal. crate:: is the most readable for absolute paths inside the current crate; self:: for relative; foo::Bar (without prefix) only works if foo is an external crate or in use scope.
  7. Renaming a module is a breaking change if the module is pub. Bump the major version, or provide a deprecation alias: pub mod old_name { pub use crate::new_name::*; }.
  8. mod.rs is not deprecated. It's required for Edition 2015 and still works in 2018+. The modern foo.rs + foo/ layout is preferred for new code but mod.rs is not wrong.

On-Demand Resources

  • Layout templates with copy-paste skeletons
  • Flat-lib.rs refactoring walkthrough
  • Large crate layout (50-file worked example)
  • Migrating foo/mod.rs → foo.rs + foo/
  • Java/Python → Rust module mindset
  • Public facade design (C-REEXPORT)
  • Naming conventions

Official References

FAQ

What does the rust-module-layout skill do?

Design and enforce the internal layout of a single Rust crate — lib.rs/main.rs as a thin index, semantically named directory modules, mod declarations, pub visibility, and targeted re-exports. Use when users ask how to organize src/, write lib.rs, split a growing crate into subdirectories, choose between foo.rs and foo/mod.rs, name modules, avoid flat lib.rs "index plus glob re-export" anti-patterns, or migrate Java-style or Python-style flat projects to idiomatic Rust module trees.

How do I install rust-module-layout?

Run npx skills add https://github.com/full-stack-skills/rust-skills --skill rust-module-layout in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/rust-module-layout/ (personal) or .claude/skills/rust-module-layout/ (project).

Which AI agents can use rust-module-layout?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is rust-module-layout safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains rust-module-layout?

It is published by full-stack-skills in full-stack-skills/rust-skills. It has 115 recorded installs.

More from full-stack-skills/rust-skills

View all →

rust-testing

full-stack-skills/rust-skills

Design, implement, and validate Rust tests, including unit, integration, doctest, compile-fail, property, fuzz, benchmark, coverage, async, concurrency, process, daemon, IPC, terminal, platform, and hardware-facing test strategies. Use when users ask for Rust test architecture, flaky-test diagnosis, coverage gates, benchmarks, trybuild, cargo-nextest, real-process tests, or failure-path verification.

Testing & QA148 installs

rust-concurrency

full-stack-skills/rust-skills

Open the skill to read its SKILL.md instructions.

Backend & APIs137 installs

rust-style-clippy

full-stack-skills/rust-skills

Open the skill to read its SKILL.md instructions.

Backend & APIs137 installs

rust-performance

full-stack-skills/rust-skills

Open the skill to read its SKILL.md instructions.

Backend & APIs135 installs

rust-code-review

full-stack-skills/rust-skills

Open the skill to read its SKILL.md instructions.

rust-cargo-build

full-stack-skills/rust-skills

Open the skill to read its SKILL.md instructions.

Backend & APIs123 installs

Popular backend & apis skills

View category →

find-skills

vercel-labs/skills

Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.

OfficialBackend & APIs4.1M installs

entra-app-registration

microsoft/azure-skills

Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.

OfficialBackend & APIs566.7K installs

This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).

Backend & APIs514.8K installs

embedded-captions

heygen-com/hyperframes

Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.

Backend & APIs453.3K installs

entra-agent-id

microsoft/azure-skills

Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).

OfficialBackend & APIs336.6K installs

neon

neondatabase/agent-skills

Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.

Backend & APIs237.1K installs