DaleSchool

Organizing Code: Modules and Crates

Intermediate15min

Learning Objectives

  • Define modules with mod and import them with use
  • Control visibility with the pub keyword
  • Add external crates to Cargo.toml and use them
  • Find crates on crates.io

Working Code

As code grows, putting everything in one file gets messy. Modules let you organize code into logical groups.

mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn multiply(a: i32, b: i32) -> i32 {
        a * b
    }

    fn secret_formula(x: i32) -> i32 {
        x * 42  // private function!
    }
}

fn main() {
    let sum = math::add(3, 5);
    let product = math::multiply(4, 6);
    println!("3 + 5 = {sum}");
    println!("4 x 6 = {product}");
    // math::secret_formula(1); // error! private
}

mod math creates a module. You call its functions with the path math::add().

Try It Yourself

  1. Uncomment math::secret_formula(1). What error do you get?
  2. Add pub to secret_formula. Does the error go away?

"Why?" — pub and Private by Default

In Rust, items inside a module are private by default. You must explicitly add pub to make them accessible from outside.

This follows the same philosophy as ownership — safety is the default. Expose only what's necessary and hide the rest.

| Keyword | Meaning | | ------- | ------------------------------------------------ | | (none) | Private — accessible only within the same module | | pub | Public — accessible from outside |

use — Shorter Paths

If you want to skip writing math::add() every time, use use:

mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn multiply(a: i32, b: i32) -> i32 {
        a * b
    }
}

use math::add;
use math::multiply;

fn main() {
    println!("3 + 5 = {}", add(3, 5));
    println!("4 x 6 = {}", multiply(4, 6));
}

You can import multiple items at once:

use math::{add, multiply};

Splitting Modules Into Files

As code grows further, you can move modules to separate files. This is the most common pattern in real projects.

src/
├── main.rs
└── math.rs

src/math.rs:

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

src/main.rs:

mod math;  // loads the math.rs file as a module

fn main() {
    println!("3 + 5 = {}", math::add(3, 5));
}

Just write mod math; and Rust finds and links the math.rs file automatically.

Using External Crates

You used the rand crate in Lesson 07. External crates are available on crates.io.

cargo add rand

This command automatically adds the crate to the [dependencies] section of Cargo.toml:

[dependencies]
rand = "0.8"

Then import and use it:

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let number: i32 = rng.gen_range(1..=100);
    println!("Random number: {number}");
}

Standard Library Modules

Rust comes with a standard library (std) full of commonly used features. Import them with use — no installation needed.

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("Alice", 95);
    scores.insert("Bob", 88);

    for (name, score) in &scores {
        println!("{name}: {score}");
    }
}

Commonly used standard library modules:

| Module | Purpose | | ------------------ | ----------------------- | | std::io | Input/output | | std::collections | HashMap, BTreeMap, etc. | | std::fs | File read/write | | std::fmt | Formatting |

Learn More: Finding Crates on crates.io

crates.io is Rust's package registry with tens of thousands of crates.

Here are some popular ones:

| Crate | Purpose | | --------- | ----------------------------------------------- | | serde | Data serialization/deserialization (JSON, etc.) | | tokio | Async runtime | | clap | Command-line argument parsing | | reqwest | HTTP requests |

When choosing a crate, check its download count and last update date. Actively maintained crates are the safest bet.

Learn More: Module Paths and the crate Keyword

Module paths have two starting points:

// Absolute path: starts from crate
crate::math::add(1, 2);

// Relative path: from current location
math::add(1, 2);

// Parent module reference
super::some_function();

crate is a keyword pointing to the root of the current project. For large projects with complex paths, absolute paths can be clearer.

Exercise 1. Create a geometry module with circle_area and rectangle_area functions.

mod geometry {
    // circle_area: takes radius, returns area of a circle
    // rectangle_area: takes width and height, returns area
    // Both should be pub!
    // Hint: PI is std::f64::consts::PI
}

fn main() {
    println!("Circle: {:.2}", geometry::circle_area(5.0));
    println!("Rectangle: {:.2}", geometry::rectangle_area(4.0, 6.0));
}

Exercise 2. Use use to shorten the code below.

fn main() {
    let mut map = std::collections::HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);

    let mut set = std::collections::HashSet::new();
    set.insert("x");
    set.insert("y");

    println!("{:?}", map);
    println!("{:?}", set);
}

Hint: use std::collections::{HashMap, HashSet};

Q1. Functions inside a Rust module are, by default:

  • A) Public
  • B) Private
  • C) Read-only
  • D) Constant

Q2. The command to add an external crate to a project is:

  • A) cargo install crate
  • B) cargo add crate
  • C) cargo get crate
  • D) cargo import crate

Q3. Why does this code produce an error?

mod secrets {
    fn hidden() -> i32 { 42 }
}

fn main() {
    println!("{}", secrets::hidden());
}
  • A) The mod syntax is wrong
  • B) The hidden function is private
  • C) The return type is wrong
  • D) The secrets module doesn't exist