Why this matters
As programs grow past a few hundred lines, one flat file of functions becomes hard to navigate, and anything can call anything. Modules group related code under a name and control what is visible from outside. Privacy is not just tidiness: a module that keeps its fields private can guarantee invariants (a temperature never below absolute zero, a sorted list that stays sorted), because no outside code can break them. This lesson uses inline modules in a single file, which is what the exercises support, and then shows how the same structure maps onto files in a cargo project.
Defining modules
mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub mod stats {
pub fn mean(v: &[f64]) -> f64 {
v.iter().sum::<f64>() / v.len() as f64
}
}
fn secret() -> i32 {
42
}
pub fn reveal() -> i32 {
secret()
}
}
fn main() {
println!("{}", math::add(2, 3));
println!("{}", math::stats::mean(&[1.0, 2.0, 6.0]));
println!("{}", math::reveal());
}
5
3
42
mod name { ... }creates a module; modules nest.- Items are reached with paths joined by
::, likemath::stats::mean. - Everything is private by default: visible only inside its own module (and that module's children).
pubmakes it visible to the parent and beyond.
Using a private item from outside:
mod math {
fn secret() -> i32 {
42
}
}
fn main() {
println!("{}", math::secret());
}
error[E0603]: function `secret` is private
--> main.rs:8:26
|
8 | println!("{}", math::secret());
| ^^^^^^ private function
|
note: the function `secret` is defined here
--> main.rs:2:5
|
2 | fn secret() -> i32 {
| ^^^^^^^^^^^^^^^^^^
E0603 points at the private item. The rule of thumb is privacy flows down, not up: children can see their ancestors' private items, parents cannot see their children's.
Structs: public type, private fields
pub on a struct makes the type public, but each field stays private unless marked pub too:
mod bank {
pub struct Account {
pub owner: String,
balance: i64,
}
impl Account {
pub fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
pub fn deposit(&mut self, amount: i64) {
if amount > 0 {
self.balance += amount;
}
}
pub fn balance(&self) -> i64 {
self.balance
}
}
}
fn main() {
let mut a = bank::Account::new("Ada");
a.deposit(100);
a.deposit(-50);
println!("{} has {}", a.owner, a.balance());
}
Ada has 100
Outside bank, nobody can write a.balance = -1_000_000 or construct an Account with a literal, so the only way to change the balance is through deposit, which enforces the rule. Trying:
mod bank {
pub struct Account {
pub owner: String,
balance: i64,
}
}
fn main() {
let a = bank::Account { owner: String::from("Eve"), balance: 1_000_000 };
}
error[E0451]: field `balance` of struct `Account` is private
--> main.rs:9:57
|
9 | let a = bank::Account { owner: String::from("Eve"), balance: 1_000_000 };
| ^^^^^^^^^^^^^^^^^^ private field
Enums are different: if an enum is pub, all its variants are public, since an enum with hidden variants could not be matched.
Paths: crate, self, super
Paths can be absolute (starting from the crate root with crate::) or relative (starting from the current module, or from the parent with super::):
mod shop {
pub fn tax(amount: f64) -> f64 {
amount * 0.08
}
pub mod cart {
pub fn total(prices: &[f64]) -> f64 {
let sum: f64 = prices.iter().sum();
sum + super::tax(sum)
}
pub fn total_absolute(prices: &[f64]) -> f64 {
let sum: f64 = prices.iter().sum();
sum + crate::shop::tax(sum)
}
}
}
fn main() {
println!("{:.2} {:.2}", shop::cart::total(&[10.0, 15.0]), shop::cart::total_absolute(&[10.0, 15.0]));
}
27.00 27.00
self:: means the current module (rarely needed). Prefer super:: for close relatives and crate:: for things far away; absolute paths keep working when you move a module around.
use: shortening paths
use brings a path into scope, so you can write the last part only:
mod shapes {
pub mod circle {
pub fn area(r: f64) -> f64 {
3.14159 * r * r
}
}
pub mod square {
pub fn area(s: f64) -> f64 {
s * s
}
}
}
use shapes::circle;
use shapes::square::area as square_area;
use std::collections::{BTreeMap, HashSet};
fn main() {
println!("{:.2} {}", circle::area(1.0), square_area(3.0));
let mut m = BTreeMap::new();
m.insert("k", 1);
let s: HashSet<i32> = [1, 2].into_iter().collect();
println!("{:?} {}", m, s.len());
}
3.14 9
{"k": 1} 2
use a::b::c;makescavailable;use a::b::{c, d};imports several;asrenames.- Convention: import the module for functions (
circle::areareads clearly) and the item for types (BTreeMap). useonly creates a shortcut in the current module; it does not change visibility.pub usere-exports an item, so users can reach it through a shorter public path even if it lives deep inside. Libraries use it to present a clean API independent of their internal file layout.
Other visibility levels
pub(crate): visible anywhere in this crate, but not to other crates. Ideal for internal helpers shared between modules of one program or library.pub(super): visible to the parent module only.
Modules in files (cargo projects)
In a real project, modules usually live in separate files. The module tree is the same; only where the code sits changes:
src/
main.rs // contains: mod geometry; mod parser;
geometry.rs // the body of mod geometry
parser/
mod.rs // the body of mod parser (or src/parser.rs), contains: mod lexer;
lexer.rs // the body of mod parser::lexer
mod geometry; with a semicolon (no braces) tells the compiler "load this module's body from geometry.rs". The crate root is src/main.rs for a binary or src/lib.rs for a library; a package can have both, and the binary then uses the library as an external crate by its package name. A package (one Cargo.toml) contains one or more crates; each crate is a tree of modules. Everything you learned with inline modules applies unchanged.
Common mistakes
- E0603 private function/struct: add
pubto the item (and to every module on the path). - E0616 private field or E0451 when constructing: provide a
pub fn newand accessor methods instead of making fields public. - E0433 failed to resolve: use of undeclared crate or module: wrong path. Check
crate::versus relative paths. - Making everything
pub: you lose the ability to change internals and to enforce invariants. Start private, open up deliberately.
Recap
mod name { }groups items; paths use::; items are private by default;pubexposes them.- Children see ancestors' private items, not the other way round.
pub structwith private fields pluspub fn newand methods enforces invariants.crate::(absolute),super::(parent),self::(current) start paths;useshortens them;pub usere-exports.pub(crate)limits visibility to the crate;mod x;loads a module fromx.rsin cargo projects.
Going further: read the Rust Book chapter 7, "Managing Growing Projects with Packages, Crates, and Modules". Then create a cargo project in the sandbox shell and move a module from main.rs into its own file.