Lesson 10: Modules and Packages
Introduction
As programs grow, keeping all code in a single file becomes hard to manage. Rust provides a powerful module system to organise code into logical units, and Cargo — Rust’s build tool and package manager — to manage dependencies and build your projects. In this lesson you’ll learn how to structure code with mod, control visibility with pub, and bring items into scope with use.
Defining Modules with mod
A mod block groups related functions, structs, and other items together. By default, everything inside a module is private — use pub to make items accessible from outside.
mod greetings {
// Private function — only usable inside this module
fn helper() -> &'static str {
"Hello"
}
// Public function — accessible from outside
pub fn say_hello(name: &str) {
println!("{}, {}!", helper(), name);
}
pub fn say_goodbye(name: &str) {
println!("Goodbye, {}!", name);
}
}
fn main() {
greetings::say_hello("Alice");
greetings::say_goodbye("Bob");
// greetings::helper(); // ERROR: private function
}- Use
mod name { ... }to define a module inline. - Call items with
module_name::item_name— the::path operator. - Items are private by default; add
pubto expose them.
Nested Modules
Modules can be nested inside other modules, creating a hierarchy. Use super to refer to the parent module and crate to refer to the root of the current crate.
mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
std::f64::consts::PI * radius * radius
}
}
pub mod rectangle {
pub fn area(width: f64, height: f64) -> f64 {
width * height
}
}
}
fn main() {
let c = shapes::circle::area(5.0);
let r = shapes::rectangle::area(4.0, 6.0);
println!("Circle area: {:.2}", c);
println!("Rectangle area: {:.2}", r);
}Bringing Items into Scope with use
Typing full paths every time is verbose. The use keyword brings items into the current scope so you can refer to them by shorter names.
mod maths {
pub fn square(x: i32) -> i32 { x * x }
pub fn cube(x: i32) -> i32 { x * x * x }
}
// Bring specific items into scope
use maths::square;
// Bring multiple items at once
use maths::{square, cube};
// Bring everything with * (use sparingly)
use maths::*;
fn main() {
println!("4 squared = {}", square(4));
println!("3 cubed = {}", cube(3));
}use module::itembrings a single item into scope.use module::{a, b}brings multiple items in one statement.- Avoid
use module::*(glob imports) in large codebases — it makes it hard to know where names come from.
Package Management with Cargo
Cargo is Rust’s official build tool and package manager. It handles creating projects, building, testing, and adding external libraries (called crates) from crates.io.
# Create a new project
cargo new my_project
cd my_project
# Build the project
cargo build
# Run the project
cargo run
# Run tests
cargo test
# Check for errors without building
cargo checkAdding External Crates
To use an external library, add it to your Cargo.toml file under [dependencies]. Cargo downloads and compiles it automatically.
# Cargo.toml
[package]
name = "my_project"
version = "0.1.0"
edition = "2024"
[dependencies]
rand = "0.8" # Add the rand crate for random numbersThen use it in your code:
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let number: i32 = rng.gen_range(1..=100);
println!("Random number: {}", number);
}- Run
cargo buildafter editingCargo.toml— Cargo downloads and compiles the dependency automatically. - All dependencies are pinned in
Cargo.lockfor reproducible builds. - Browse available crates at crates.io.
Interactive Task 1: Build a Module
Create a module called converter with two public functions: km_to_miles(km: f64) and miles_to_km(miles: f64). Call both from main and print the results. Use use to bring them into scope.
mod converter {
// Your functions here
}
use converter::{km_to_miles, miles_to_km};
fn main() {
println!("{:.2} km = {:.2} miles", 100.0, km_to_miles(100.0));
println!("{:.2} miles = {:.2} km", 60.0, miles_to_km(60.0));
}Interactive Task 2: Nested Modules
Create a module temperature with two nested submodules: celsius and fahrenheit. Each should have a public function to_kelvin(t: f64) that converts the given temperature to Kelvin. Call both from main.
mod temperature {
pub mod celsius {
// to_kelvin: K = C + 273.15
// Your code here
}
pub mod fahrenheit {
// to_kelvin: K = (F - 32) * 5/9 + 273.15
// Your code here
}
}
fn main() {
// Call temperature::celsius::to_kelvin and temperature::fahrenheit::to_kelvin
}Mini-Challenge
Create a module stats with three public functions: mean(data: &[f64]), min(data: &[f64]), and max(data: &[f64]). Use use stats::* to bring them all into scope and call each one from main with the slice &[3.0, 7.0, 1.0, 9.0, 4.0].
mod stats {
// Your mean(), min(), and max() here
}
use stats::*;
fn main() {
let data = &[3.0, 7.0, 1.0, 9.0, 4.0];
// Call and print mean, min, max
}Recap
- mod groups related code — everything inside is private by default.
- pub makes items accessible from outside their module.
- Use
module::itempaths to access items, or use to bring them into scope. - Modules can be nested — build hierarchies with
pub mod. - Cargo is your all-in-one tool —
cargo new,cargo build,cargo run,cargo test. - Add external crates via
Cargo.tomlunder[dependencies]and browse them at crates.io.
Questions about modules or Cargo? Drop a comment!