Why Modules Exist
As a Rust project grows beyond a few hundred lines of code, keeping every struct, enum, trait, and function in a single flat namespace becomes unmanageable. Rust's module system organizes code within a crate into hierarchical namespaces and enforces privacy boundaries (encapsulation) so internal implementation details can change freely without breaking callers.
The Five Levels of Code Organization in Rust
Rust structures code using a five-level hierarchy from largest to smallest:
| Level | Construct | What It Represents |
|---|---|---|
| 1. Workspace | [workspace] in root Cargo.toml |
A collection of related packages developed together sharing one Cargo.lock and target/ |
| 2. Package | A directory with a Cargo.toml file |
One library crate (src/lib.rs) and/or one or more binary crates (src/main.rs, src/bin/*.rs) |
| 3. Crate | src/lib.rs or src/main.rs |
The unit of compilation passed to rustc (forms the root of the module tree: crate) |
| 4. Module | mod name { ... } or mod name; |
A namespace and privacy boundary inside a crate |
| 5. Item | fn, struct, enum, trait, const, type |
An individual declaration living inside a module |
Crate Root (`crate`, e.g., `src/lib.rs`)
├── mod auth (Namespace + Privacy Boundary)
│ ├── pub struct Token (Public: visible outside `auth`)
│ └── fn hash_secret() (Private: hidden inside `auth`!)
└── mod storage (Namespace + Privacy Boundary)
├── pub struct Record (Public: visible outside `storage`)
└── struct PageHeader (Private: hidden inside `storage`!)
Four Problems Solved by Modules
1. Preventing Name Collisions (Namespacing)
Without modules, defining two types with the same name—such as a Config for your database connection and a Config for your HTTP server—causes a duplicate definition error (E0428).
Placing each subsystem in its own module gives every item a distinct hierarchical path (database::Config vs http::Config):
mod database {
pub struct Config {
pub pool_size: u32,
}
}
mod http {
pub struct Config {
pub port: u16,
}
}
fn main() {
let db_cfg = database::Config { pool_size: 16 };
let web_cfg = http::Config { port: 8080 };
println!("DB pool: {}, HTTP port: {}", db_cfg.pool_size, web_cfg.port);
}
DB pool: 16, HTTP port: 8080
2. Enforcing Encapsulation and Invariants (Privacy by Default)
In Rust, every item and struct field inside a module is private by default. External code outside a module can only access items explicitly marked with the pub keyword.
Because a module's private fields cannot be modified directly from outside that module, the module can guarantee type invariants (for example, ensuring a Percentage value is always between 0 and 100 or an email address is always validated at construction):
mod PercentageModule {
pub struct Percentage {
value: u8, // Private field! External code cannot write `Percentage { value: 250 }`
}
impl Percentage {
pub fn new(value: u8) -> Option<Self> {
if value <= 100 {
Some(Self { value })
} else {
None
}
}
pub fn get(&self) -> u8 {
self.value
}
}
}
fn main() {
let valid = PercentageModule::Percentage::new(85).unwrap();
let invalid = PercentageModule::Percentage::new(150);
println!("Valid percentage: {}%, invalid(150): {}", valid.get(), invalid.is_none());
}
Valid percentage: 85%, invalid(150): true
3. Separating Public API from Internal Implementation
In a library crate, modules let you organize code across dozens of internal helper modules while using pub use (re-exports) at the crate root to expose a clean, flat public API to users. You can refactor, rename, or split internal private functions and modules at any time without causing a breaking version change for downstream consumers.
4. Decoupling Module Hierarchy from Physical Files
Unlike Python, Java, or Go—where the filesystem directory layout rigidly dictates package namespaces—Rust decouples the logical module tree (mod declarations) from physical files. You can prototype an entire module hierarchy inline inside a single file using mod name { ... } blocks, and later move any module into its own .rs file simply by replacing { ... } with a semicolon (mod name;) without changing a single path anywhere else in the crate.