When the code grows, it cannot all fit in one file. Rust divides code into a tree structure with modules (mod), controls visibility with pub, and fetches external crates from Cargo (crates.io). In this section, we cover inline, file, and directory module splitting patterns, use and visibility, and adding dependencies in Cargo.
Learning Objectives
- Define a module with the
modkeyword and control visibility withpub. - Know the two standard patterns for splitting modules into files or directories.
- Shorten paths and add aliases with
use. - Add external crates to the
[dependencies]ofCargo.toml.
Key Concepts
1) Modules are Trees
A crate (binary or library) starts from a root file (src/main.rs or src/lib.rs) and has child modules in a tree structure.
crate
├── math
│ ├── add
│ └── double
└── text
├── shout
└── whisper
2) Inline Modules
Create a module within the same file with mod name { ... }. This is the simplest method for small code.
mod math {
pub fn add(a: i32, b: i32) -> i32 { a + b }
}
math::add(1, 2);
3) Splitting by Files · Directories
When code gets large, split it using one of the following two methods.
File Split — When you declare mod math;, the compiler looks for math.rs.
src/
├── main.rs // mod math;
└── math.rs // pub fn add(...)
Directory Split — If a module has child modules, create a directory and put the body in mod.rs or <module_name>.rs.
src/
├── main.rs // mod geometry;
└── geometry/
├── mod.rs // pub mod circle; pub mod square;
├── circle.rs
└── square.rs
(From edition 2018+ onward, instead of mod.rs, the form geometry.rs + geometry/circle.rs is also possible.)
4) Visibility — pub
- By default, it is private. You need to attach
pubfor it to be visible from the parent. pub(crate): Visible only within the same crate.pub(super): Visible only in the parent module.pub(in path): Visible only within the specified path.
5) Path shortening with use
use crate::math::{add, double}; //Inside the same crate
use std::collections::HashMap; //Standard library
use rand::Rng; //External crate (after adding dependency)
use crate::math as m; //Alias
super::, self::, and crate:: are keywords referring to parent, self, and root, respectively.
6) Cargo dependencies
Cargo.toml:
[dependencies]
serde = "1"
rand = { version = "0.8", features = ["std_rng"] }
You can also add them via command:
cargo add serde@1
cargo add rand --features std_rng
Version notation "1" is the same as ^1.0.0 — meaning it is within the semver compatible range.
Examples
Example 1 — ex01_inline.rs: Inline module and visibility
//Example 1 of Lesson 19: Inline Modules and pub Visibility
//
//You can define modules inside the same file using the `mod` keyword.
//Items inside a module are private by default — you need to add `pub` for them to be visible from outside.
mod math {
//Without pub, they cannot be called from outside the module.
fn _internal() -> i32 { 42 }
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn double(x: i32) -> i32 {
x * 2
}
}
fn main() {
//From outside, call using `module_name::function_name`.
let sum = math::add(3, 4);
let twice = math::double(sum);
println!("(3+4)*2 = {twice}");
//math::_internal(); // ❌ Cannot call because it is private
}
Example 2 — ex02_pub_use.rs: Path shortening with use
//Example 2 of Chapter 19: Shortening paths with use
//
//Frequently used items can be imported with `use` and referenced by short names to improve readability.
mod text {
pub fn shout(s: &str) -> String {
s.to_uppercase() + "!"
}
pub fn whisper(s: &str) -> String {
format!("({})", s.to_lowercase())
}
}
//Bringing module functions into the current scope
use text::{shout, whisper};
//You can also import your own module with an alias.
use text as tt;
fn main() {
println!("{}", shout("hello"));
println!("{}", whisper("Quiet"));
//Calling using an alias as well
println!("{}", tt::shout("hi"));
}
Example 3 — ex03_nested.rs: Nested modules
//Example 3 from Chapter 19: Nested Modules and super
//
//Modules are tree-structured, so you can access parent from child using `super::`.
//Also, sibling modules of the same depth access each other via the parent.
mod geometry {
pub mod circle {
use std::f64::consts::PI;
pub fn area(r: f64) -> f64 {
//To access the parent's sibling (square), use super::square::...
PI * r * r
}
}
pub mod square {
pub fn area(side: f64) -> f64 {
side * side
}
}
}
//Use `use` to shorten deep paths
use geometry::{circle, square};
fn main() {
println!("circle area (r=2) = {:.2}", circle::area(2.0));
println!("square area (s=3) = {:.2}", square::area(3.0));
}
Common Mistakes
Q. I wrote mod math;, but the compiler cannot find the file.
A. If src/main.rs is the root, the compiler looks for src/math.rs or src/math/mod.rs. If there is another child within a child module, the directory structure must match.
Q. What is pub use?
A. Re-expose items from other places through your module's public API. This is used when you want to use items from deep internal modules of a library directly from the root.
Q. How do you read the external crate version specification?
A. It follows the semver (major.minor.patch) convention. "1" is the latest compatible version of 1.x for ^1.0.0, "=1.2.3" is exactly 1.2.3, and "~1.2" is 1.2.x.
Q. What's the difference between cargo build and cargo build --release?
A. The default is a debug build (target/debug/) — fast compilation, slow execution. --release is an optimized build (target/release/) — slow compilation, fast execution. Used right before distribution.
Summary
- Modules are tree-structured — defined with
mod, visibility controlled withpub. - Small modules are inlined; larger ones are split into files/directories.
- Shorten paths with
useand alias withas. - External crates are added with
Cargo.tomlusing[dependencies]orcargo add.
Try it yourself
Solve the assignment below. The reference solution is shown below.