File mods
Goal of This Episode
Learn to split mods into separate files, and understand Rust’s file-to-mod correspondence rules.
Concept
Last episode we wrote mods inside one file, but real projects can’t stuff everything together. Rust provides rules for splitting mods into standalone files.
The Basic Split: mod + a Standalone File
Suppose you have a math mod and want to move it into its own file. The recipe is simple:
- In
main.rs(orlib.rs) writemod math;(note the trailing semicolon, not braces). - Create
math.rsand put themod’s contents in it.
src/
├── main.rs
└── math.rs
main.rs:
mod math;
fn main() {
let result = math::add(3, 5);
println!("3 + 5 = {}", result);
}
math.rs:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn subtract(a: i32, b: i32) -> i32 {
a - b
}
Note that inside math.rs you don’t write mod math { ... } again — the file itself is that mod.
Folder Structures for Sub-mods
If the math mod has sub-mods of its own, there are two ways to organize:
Way 1: with mod.rs (the traditional style)
src/
├── main.rs
└── math/
├── mod.rs
├── basic.rs
└── advanced.rs
math/mod.rs is the math mod’s entry point, declaring the sub-mods:
// math/mod.rs
pub mod basic;
pub mod advanced;
Way 2: a same-named file + folder (recommended)
src/
├── main.rs
├── math.rs ← The math mod's entry point
└── math/
├── basic.rs
└── advanced.rs
// math.rs
pub mod basic;
pub mod advanced;
Both ways work identically — pick whichever you like. Newer projects lean toward Way 2, avoiding a pile of files all named mod.rs that are hard to tell apart in an editor.
lib.rs vs main.rs
A Rust project can contain one or more crates. A crate comes in two types:
- binary
crate: hassrc/main.rs, compiling to an executable. - library
crate: hassrc/lib.rs, a library for others to use.
One project can contain both main.rs and lib.rs. main.rs is the binary crate’s root; lib.rs is the library crate’s root.
src/
├── main.rs ← binary crate root
├── lib.rs ← library crate root
├── math.rs
└── math/
├── basic.rs
└── advanced.rs
Inside main.rs, refer to things in lib.rs via the crate’s name:
// main.rs
// Assuming Cargo.toml's [package] name = "my_project"
use my_project::math;
fn main() {
let result = math::basic::add(1, 2);
println!("{}", result);
}
Example Code
Since file mods span multiple files, a single-file demo isn’t possible. Below is a complete multi-file example — create the corresponding file structure and run it with cargo run:
src/
├── main.rs
├── math.rs
└── math/
├── basic.rs
└── advanced.rs
main.rs:
mod math;
fn main() {
let sum = math::basic::add(10, 20);
println!("10 + 20 = {}", sum);
let p = math::advanced::power(2, 8);
println!("2 ^ 8 = {}", p);
}
math.rs:
pub mod basic;
pub mod advanced;
math/basic.rs:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
math/advanced.rs:
pub fn power(base: i32, exp: u32) -> i32 {
let mut result = 1;
for _ in 0..exp {
result *= base;
}
result
}
Recap
mod math;(semicolon-terminated) tells Rust to go find the sub-mod.- The split-out file doesn’t contain another
mod math { ... }— the file itself is themod. - Sub-
mods can usemath/mod.rs(traditional) ormath.rs+ amath/folder (recommended). main.rsis the binarycrate’s root;lib.rsis the librarycrate’s root.- One project can contain a binary
crateand a librarycrateat the same time.