Rust Essential Course – Lesson 10

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 pub to 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::item brings 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 check

Adding 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 numbers

Then 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 build after editing Cargo.toml — Cargo downloads and compiles the dependency automatically.
  • All dependencies are pinned in Cargo.lock for 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));
}

Try here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=4639b0b39e7c7b0421d418dc285019da

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
}

Try here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=747104525777be632e3ebeb2a3a0150b

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
}

Solution: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=46470d193424c67e808689839638f1c7

Recap

  • mod groups related code — everything inside is private by default.
  • pub makes items accessible from outside their module.
  • Use module::item paths 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.toml under [dependencies] and browse them at crates.io.

Questions about modules or Cargo? Drop a comment!

Leave a Comment

Your email address will not be published. Required fields are marked *