Skip to content

Rust Modules

Every example so far has lived in one main.rs file. That’s fine for small programs, but real projects split code into logical pieces. Rust’s tool for that is the module system — mod, pub, and use.

A module is a named block that groups related code together:

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

Output:

5
▶ Try it Yourself

Two things to notice: the function is called with math::add(...), using the module name as a path. And it’s marked pub — without that, it wouldn’t be reachable from outside the module.

This is worth calling out because it surprises people: unlike most things in Rust, module contents are private by default. If you remove pub from the example above, it won’t compile:

mod math {
fn add(a: i32, b: i32) -> i32 { // no `pub`
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ error: function `add` is private
println!("{sum}");
}

You have to explicitly mark anything you want callers outside the module to use with pub. This is intentional — it lets a module hide its internal helper functions and only expose the parts meant to be a public API.

Modules can contain other modules, which is how larger projects build up a tree-like structure:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ Try it Yourself

Writing shapes::circle::area(...) every time gets tedious. use brings a path into scope so you can refer to it directly:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // shorter than shapes::circle::area
println!("{area}");
}
▶ Try it Yourself

You’ve actually already used use — it’s the same keyword from the HashMap page (use std::collections::HashMap;).

The examples above put everything in one file, which the Rust Playground requires. In a real project on your own machine, each module usually gets its own file. If you had a project with a math module, it would typically look like this:

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // tells Rust: look for this module's code in math.rs
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math; (with a semicolon, no body) tells Rust to load the module’s contents from math.rs in the same directory. This is the pattern you’ll use as soon as a project grows beyond a single file.

Make a module called convert with two public functions: c_to_f (Celsius to Fahrenheit) and f_to_c (the other way). From main, print 100°C is 212°F and 212°F is 100°C.

Hint: the formulas are c * 9.0 / 5.0 + 32.0 and (f - 32.0) * 5.0 / 9.0. Don’t forget pub, and call the functions with the module name, like convert::c_to_f(100.0).

Show solution
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C is {}°F", convert::c_to_f(100.0));
println!("212°F is {}°C", convert::f_to_c(212.0));
}

Output:

100°C is 212°F
212°F is 100°C
▶ Try it Yourself

Both functions are pub, so main (which lives outside the module) can call them. Remove one pub and the compiler tells you that function is private.

Make a nested module school::grades with a public function letter(score: u32) -> char that returns 'A' for 90 and up, 'B' for 80 and up, and 'C' otherwise. Inside grades, also write a private helper is_at_least(score: u32, min: u32) -> bool that letter uses. Bring school::grades into scope with use, then print the letter for 95, 85 and 70.

Hint: both school’s inner mod grades and fn letter need pub, but is_at_least should not have it. Code inside the same module can always call private functions.

Show solution
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// private: only code inside `grades` can call this
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

Output:

95 -> A
85 -> B
70 -> C
▶ Try it Yourself

letter can call is_at_least because they live in the same module. From main, grades::is_at_least(95, 90) would fail with “function is_at_least is private”. That’s the point: the module shows a small public API and keeps its helpers to itself.

  • mod name { ... } groups related code into a named module.
  • Everything inside a module is private by default — add pub to expose it outside the module.
  • use brings a path into scope so you don’t have to write it out in full every time.
  • In a multi-file project, mod name; (no body) loads that module’s code from a matching file.
  • Code inside a module can always use that module’s private items, so keep helpers private and expose only what callers need.

Quick check

1. By default, are items inside a mod public or private?

2. How do you tell Rust to load a module's code from math.rs instead of writing it inline?

Score: 0 / 2