Question
In several Rust projects, such as pczarn/rustboot, directories contain files named mod.rs. What is the purpose of a mod.rs file in Rust, and when should it be used?
Short Answer
You will learn how Rust organizes modules across files and directories, why mod.rs was historically used as a directory module's entry file, and why modern Rust code usually uses module_name.rs instead. You will also see how to declare child modules and control their visibility.
Concept
Rust uses a module system to organize code into namespaces and files. A module can contain functions, structs, constants, submodules, and visibility rules.
A mod.rs file is the traditional file that represents a module stored in a directory. For example, this layout:
src/
├── network/
│ ├── mod.rs
│ ├── client.rs
│ └── server.rs
└── main.rs
can be connected from main.rs like this:
mod network;
Rust reads src/network/mod.rs as the contents of the network module. That file can then declare its child modules:
mod client;
mod server;
Historically, mod.rs was required when a module had its own directory. Since Rust 2018, Rust also supports the more direct layout below:
src/
├── network.rs
└── network/
├── client.rs
└── server.rs
// src/network.rs
mod client;
mod server;
This modern style is generally preferred because network.rs clearly names the network module. A mod.rs file is still valid Rust, so you will often encounter it in older projects or projects that retain the older convention.
Mental Model
Think of a module as a labeled folder in a filing cabinet.
networkis the folder label.network.rsornetwork/mod.rsis the folder's index page.client.rsandserver.rsare documents inside that folder.
The index page decides which documents belong to the folder by declaring them with mod client; and mod server;.
Both network.rs and network/mod.rs can serve as the index page. The modern style puts the module name in the filename; the older style uses the generic name mod.rs inside the module directory.
Syntax and Examples
A module declaration uses the mod keyword:
mod helpers;
If Rust finds this declaration in src/main.rs, it looks for one of these files:
src/helpers.rs
src/helpers/mod.rs
Modern directory module layout
src/
├── main.rs
├── helpers.rs
└── helpers/
└── text.rs
// src/main.rs
mod helpers;
fn main() {
println!("{}", helpers::text::shout("hello"));
}
// src/helpers.rs
pub mod text;
// src/helpers/text.rs
pub fn shout(message: &str) -> String {
message.to_uppercase()
}
pub mod text; both declares the text child module and makes it accessible from outside helpers. pub fn shout makes the function accessible from outside text.
Equivalent traditional mod.rs layout
src/
├── main.rs
└── helpers/
├── mod.rs
└── text.rs
// src/helpers/mod.rs
pub mod text;
The main.rs and text.rs code can stay exactly the same. Only the location of the parent module's source file changed.
Step by Step Execution
Consider this project:
src/
├── main.rs
├── math.rs
└── math/
└── add.rs
// src/main.rs
mod math;
fn main() {
let total = math::add::two(4);
println!("{total}");
}
// src/math.rs
pub mod add;
// src/math/add.rs
pub fn two(value: i32) -> i32 {
value + 2
}
Execution and compilation proceed as follows:
- Rust starts with
main.rs, the crate root for a binary crate. mod math;tells Rust to load themathmodule frommath.rsormath/mod.rs.- Rust loads
math.rs. pub mod add;tells Rust to loadmath/add.rsormath/add/mod.rsand exposesaddthroughmath.- Rust loads
math/add.rs. maincallsmath::add::two(4).tworeturns6, so the program prints6.
If math.rs were replaced with math/mod.rs, the module path math::add::two and program output would be unchanged.
Real World Use Cases
Modules and directory modules are useful when one feature grows beyond a single source file.
- HTTP client: Put request construction, authentication, and response parsing under an
apimodule. - Command-line application: Place each command in modules such as
commands::build,commands::test, andcommands::clean. - Web server: Group routes, middleware, database code, and application state into separate modules.
- Game or simulation: Keep physics, rendering, input, and world-state code in separate modules.
- Library crate: Expose a small public API from a parent module while keeping implementation details in private child modules.
For example, a library might use this structure:
src/
├── lib.rs
├── parser.rs
└── parser/
├── lexer.rs
└── tokens.rs
The parser.rs file can expose only the types users need while keeping lower-level parsing helpers private.
Real Codebase Usage
In real Rust projects, a parent module file commonly acts as an API boundary. It declares child modules, chooses what is public, and may re-export convenient names.
// src/api.rs
mod internal_client;
pub mod types;
pub use internal_client::Client;
Here:
internal_clientis private toapi.typesis public, so callers can usecrate::api::types.Clientis re-exported, so callers can writecrate::api::Clientinstead of knowing its implementation module.
A typical project may also use private modules to prevent accidental coupling:
// src/config.rs
mod file_format;
pub fn load() -> Result<AppConfig, ConfigError> {
file_format::read_config()
}
Code outside config cannot call file_format::read_config() directly. It must use the intentionally designed config::load() function.
Use a directory module when a module has multiple related files. Keep a small module in one name.rs file until splitting it improves readability.
Common Mistakes
Expecting a folder to create a module automatically
Creating src/tools/format.rs is not enough. Rust needs a parent module declaration.
// src/main.rs
mod tools;
Then Rust needs either src/tools.rs or src/tools/mod.rs, containing:
pub mod format;
Declaring a child module in the wrong file
This is incorrect if format.rs is inside the tools directory:
// src/main.rs
mod format; // Rust searches for src/format.rs, not src/tools/format.rs
Declare mod format; in tools.rs or tools/mod.rs instead.
Forgetting pub
The following compiles, but format cannot be accessed from outside tools:
// src/tools.rs
mod format;
Use pub mod format; if external code should access it.
Similarly, a public module does not make every item inside it public:
pub mod format {
fn run() {} // Still private
}
Use pub fn run() when callers need the function.
Using both tools.rs and tools/mod.rs
Do not create both files for the same tools module. mod tools; would have two possible source files, which causes a compiler error. Choose one layout.
Adding mod for a module declared inline
An inline module already has its body in the same file:
mod tools {
pub fn run() {}
}
Do not also create tools.rs for that same declaration unless you change the declaration to the file-based form mod tools;.
Comparisons
| Approach | File layout | Best use | Notes |
|---|---|---|---|
| Single-file module | src/tools.rs | Small, self-contained module | Simplest layout |
| Modern directory module | src/tools.rs plus src/tools/*.rs | Module with related submodules | Preferred Rust 2018+ convention |
| Traditional directory module | src/tools/mod.rs plus src/tools/*.rs | Existing or older codebases | Still supported, but usually less clear |
| Inline module | Module body inside another .rs file | Very small local grouping | Can become hard to read if large |
mod versus use
| Keyword | Purpose |
|---|---|
mod | Defines or loads a module as part of the current crate. |
use | Brings an existing path into the current scope under a shorter name. |
mod helpers; // Loads this crate's helpers module.
use crate::helpers::text; // Makes `text` available in this scope.
mod.rs versus lib.rs and main.rs
main.rsis normally the root module of a binary crate.lib.rsis normally the root module of a library crate.mod.rsis a traditional file for a non-root module represented by a directory.name.rsis the modern preferred filename for that same non-root module.
Cheat Sheet
// Declare a file-based child module.
mod tools;
// Make a child module accessible from the parent module's users.
pub mod format;
// Make an item accessible outside its module.
pub fn run() {}
// Import an existing path into scope.
use crate::tools::format;
mod tools;loads one of:tools.rstools/mod.rs
- Use
tools.rsfor new directory-based modules in modern Rust. - Put child files beside a directory module in
tools/, such astools/format.rs. - A directory by itself is not a Rust module; it needs a parent module file and a
moddeclaration. moddeclares or loads a module;useimports a name for convenient access.- Modules and their contents are private by default. Add
pubdeliberately. - Never keep both
tools.rsandtools/mod.rsfor the same module.
FAQ
Is mod.rs required in Rust?
No. It is still supported, but Rust 2018 introduced the preferred alternative of using module_name.rs as the parent module file.
When should I use mod.rs?
Usually, use it only when following an existing project's convention or working in an older codebase. For new code, prefer module_name.rs.
What replaces mod.rs in modern Rust?
For a network module, use src/network.rs. Put its child modules in src/network/, such as src/network/client.rs.
Can I have both foo.rs and foo/mod.rs?
No. Both represent the same foo module for a mod foo; declaration, so Rust reports an ambiguity error.
Does mod foo; make foo public?
No. Modules are private by default. Use pub mod foo; when the parent module's users should access it.
Does a public module make all its functions public?
No. Items also need their own visibility, such as pub fn function_name().
Is mod.rs the crate root?
Usually not. Crate roots are typically main.rs for binary crates and lib.rs for library crates. A mod.rs file traditionally represents a module inside the crate.
Mini Project
Description
Build a small text utility feature split into a parent module and child modules. This mirrors how an application can group related functionality while exposing only a deliberate public API.
Goal
Create and use a text module that provides word-counting and message-formatting utilities.
Requirements
Create a binary Rust project with a main.rs crate root.
Create a modern directory module using src/text.rs and a src/text/ directory.
Add separate count and format child modules.
Make the required modules and functions public so main can call them.
Print a formatted message containing the number of words in a sentence.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.