Separating Code Across Files
When a module grows too large for an inline mod name { ... } block, replacing the brace body with a semicolon (mod name;) tells rustc to load the module's contents from a separate .rs source file on disk.
Where rustc Looks for mod name;
When mod network; appears in the crate root (src/main.rs or src/lib.rs), the compiler searches for the module file in two standard locations:
- Modern style (Rust 2018+ / 2024 recommended):
src/network.rs(with any child submodules ofnetworkplaced insidesrc/network/<child>.rs). - Legacy directory style:
src/network/mod.rs(with any child submodules placed alongside it insrc/network/<child>.rs).
| Declaration Location | Statement | Modern File Path (Preferred) | Legacy File Path |
|---|---|---|---|
src/main.rs or src/lib.rs |
mod network; |
src/network.rs |
src/network/mod.rs |
src/network.rs (or src/network/mod.rs) |
pub mod tcp; |
src/network/tcp.rs |
src/network/tcp/mod.rs |
src/network/tcp.rs |
pub mod frame; |
src/network/tcp/frame.rs |
src/network/tcp/frame/mod.rs |
Important: Never create both
src/network.rsandsrc/network/mod.rsin the same project. If both exist,rustcrejects the build with an ambiguity error (E0761: file for module 'network' found at both "src/network.rs" and "src/network/mod.rs").
Multi-File Project Walkthrough
Project Directory Layout (Modern Style):
my_project/
├── Cargo.toml
└── src/
├── main.rs <-- Declares `mod auth;` and `mod server;`
├── server.rs <-- Defines `crate::server`
├── auth.rs <-- Declares `pub mod token;` and defines `crate::auth`
└── auth/
└── token.rs <-- Defines `crate::auth::token`
Notice that inside src/server.rs or src/auth/token.rs, you do not write mod server { ... } or mod token { ... }—the file itself is already the body of that module!
// --- File: src/auth/token.rs ---
// pub fn verify(raw: &str) -> bool {
// raw == "secret-2024"
// }
// --- File: src/auth.rs ---
// pub mod token; // Loads `src/auth/token.rs`
//
// pub fn login(key: &str) -> Result<&'static str, &'static str> {
// if token::verify(key) {
// Ok("Session established")
// } else {
// Err("Invalid token")
// }
// }
// --- Equivalent Single-Binary Demonstration ---
mod auth {
pub mod token {
pub fn verify(raw: &str) -> bool {
raw == "secret-2024"
}
}
pub fn login(key: &str) -> Result<&'static str, &'static str> {
if token::verify(key) {
Ok("Session established")
} else {
Err("Invalid token")
}
}
}
fn main() {
println!("{:?}", auth::login("secret-2024"));
println!("{:?}", auth::login("wrong"));
}
Ok("Session established")
Err("Invalid token")
Overriding the File Path with #[path = "..."]
In rare cases—such as platform-specific module implementations (#[cfg(unix)] vs. #[cfg(windows)]) or generated code—you can override the default filename lookup using the #[path = "..."] attribute:
#[cfg(unix)]
#[path = "sys/unix.rs"]
mod sys;
#[cfg(windows)]
#[path = "sys/windows.rs"]
mod sys;