Rust track

Lesson 19 · Part 5: Cargo, Testing, Concurrency & Async

Modules & Cargo

The module system, visibility, Cargo configuration and workspaces

Part 5: Cargo, Testing, Concurrency & Async5 min readFree lesson

When the code grows, it cannot all fit in one file. Rust divides code into a tree structure with modules (mod), controls visibility with pub, and fetches external crates from Cargo (crates.io). In this section, we cover inline, file, and directory module splitting patterns, use and visibility, and adding dependencies in Cargo.

Learning Objectives

  • Define a module with the mod keyword and control visibility with pub.
  • Know the two standard patterns for splitting modules into files or directories.
  • Shorten paths and add aliases with use.
  • Add external crates to the [dependencies] of Cargo.toml.

Key Concepts

1) Modules are Trees

A crate (binary or library) starts from a root file (src/main.rs or src/lib.rs) and has child modules in a tree structure.

crate
├── math
│   ├── add
│   └── double
└── text
    ├── shout
    └── whisper

2) Inline Modules

Create a module within the same file with mod name { ... }. This is the simplest method for small code.

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

3) Splitting by Files · Directories

When code gets large, split it using one of the following two methods.

File Split — When you declare mod math;, the compiler looks for math.rs.

src/
├── main.rs           // mod math;
└── math.rs           // pub fn add(...)

Directory Split — If a module has child modules, create a directory and put the body in mod.rs or <module_name>.rs.

src/
├── main.rs           // mod geometry;
└── geometry/
    ├── mod.rs        // pub mod circle; pub mod square;
    ├── circle.rs
    └── square.rs

(From edition 2018+ onward, instead of mod.rs, the form geometry.rs + geometry/circle.rs is also possible.)

4) Visibility — pub

  • By default, it is private. You need to attach pub for it to be visible from the parent.
  • pub(crate): Visible only within the same crate.
  • pub(super): Visible only in the parent module.
  • pub(in path): Visible only within the specified path.

5) Path shortening with use

use crate::math::{add, double};   //Inside the same crate
use std::collections::HashMap;     //Standard library
use rand::Rng;                     //External crate (after adding dependency)
use crate::math as m;              //Alias

super::, self::, and crate:: are keywords referring to parent, self, and root, respectively.

6) Cargo dependencies

Cargo.toml:

[dependencies]
serde = "1"
rand = { version = "0.8", features = ["std_rng"] }

You can also add them via command:

cargo add serde@1
cargo add rand --features std_rng

Version notation "1" is the same as ^1.0.0 — meaning it is within the semver compatible range.

Examples

Example 1 — ex01_inline.rs: Inline module and visibility

//Example 1 of Lesson 19: Inline Modules and pub Visibility
//
//You can define modules inside the same file using the `mod` keyword.
//Items inside a module are private by default — you need to add `pub` for them to be visible from outside.

mod math {
    //Without pub, they cannot be called from outside the module.
    fn _internal() -> i32 { 42 }

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

    pub fn double(x: i32) -> i32 {
        x * 2
    }
}

fn main() {
    //From outside, call using `module_name::function_name`.
    let sum = math::add(3, 4);
    let twice = math::double(sum);
    println!("(3+4)*2 = {twice}");

    //math::_internal();  // ❌ Cannot call because it is private
}

Example 2 — ex02_pub_use.rs: Path shortening with use

//Example 2 of Chapter 19: Shortening paths with use
//
//Frequently used items can be imported with `use` and referenced by short names to improve readability.

mod text {
    pub fn shout(s: &str) -> String {
        s.to_uppercase() + "!"
    }

    pub fn whisper(s: &str) -> String {
        format!("({})", s.to_lowercase())
    }
}

//Bringing module functions into the current scope
use text::{shout, whisper};

//You can also import your own module with an alias.
use text as tt;

fn main() {
    println!("{}", shout("hello"));
    println!("{}", whisper("Quiet"));

    //Calling using an alias as well
    println!("{}", tt::shout("hi"));
}

Example 3 — ex03_nested.rs: Nested modules

//Example 3 from Chapter 19: Nested Modules and super
//
//Modules are tree-structured, so you can access parent from child using `super::`.
//Also, sibling modules of the same depth access each other via the parent.

mod geometry {
    pub mod circle {
        use std::f64::consts::PI;

        pub fn area(r: f64) -> f64 {
            //To access the parent's sibling (square), use super::square::...
            PI * r * r
        }
    }

    pub mod square {
        pub fn area(side: f64) -> f64 {
            side * side
        }
    }
}

//Use `use` to shorten deep paths
use geometry::{circle, square};

fn main() {
    println!("circle area (r=2)  = {:.2}", circle::area(2.0));
    println!("square area (s=3)  = {:.2}", square::area(3.0));
}

Common Mistakes

Q. I wrote mod math;, but the compiler cannot find the file.

A. If src/main.rs is the root, the compiler looks for src/math.rs or src/math/mod.rs. If there is another child within a child module, the directory structure must match.

Q. What is pub use?

A. Re-expose items from other places through your module's public API. This is used when you want to use items from deep internal modules of a library directly from the root.

Q. How do you read the external crate version specification?

A. It follows the semver (major.minor.patch) convention. "1" is the latest compatible version of 1.x for ^1.0.0, "=1.2.3" is exactly 1.2.3, and "~1.2" is 1.2.x.

Q. What's the difference between cargo build and cargo build --release?

A. The default is a debug build (target/debug/) — fast compilation, slow execution. --release is an optimized build (target/release/) — slow compilation, fast execution. Used right before distribution.

Summary

  • Modules are tree-structured — defined with mod, visibility controlled with pub.
  • Small modules are inlined; larger ones are split into files/directories.
  • Shorten paths with use and alias with as.
  • External crates are added with Cargo.toml using [dependencies] or cargo add.

Try it yourself

Solve the assignment below. The reference solution is shown below.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/modules_cargo/Cargo.tomlTOML
[package]
name = "modules_cargo"
version = "0.1.0"
edition = "2021"
default-run = "modules_cargo"

[dependencies]
src/modules_cargo/src/main.rsRust
fn main() {
    println!("This crate has three executable examples.");
    println!();
    println!("  cargo run --bin ex01_inline");
    println!("  cargo run --bin ex02_pub_use");
    println!("  cargo run --bin ex03_nested");
}

Assignment

Problem 1 — math Module

  • File name: homework_01/src/main.rs
  • Key Concepts: Inline Modules, pub, use

Requirements

  • Define the math module in the same file.
    • pub fn add(a: i32, b: i32) -> i32
    • pub fn mul(a: i32, b: i32) -> i32
  • Use the short name use math::{add, mul};.
  • Print the following in main.
    • add(3, 4) = 7
    • mul(3, 4) = 12

Expected output

add(3, 4) = 7
mul(3, 4) = 12

Hint

  • mod math { pub fn add(...) { ... } pub fn mul(...) { ... } }.

Problem 2 — shape::circle Nested Module

  • File name: homework_02/src/main.rs
  • Key Concepts: Nested Modules, Path Access

Requirements

  • Place a child module circle inside the shape module.
  • shape::circle::area(r: f64) -> f64 returns the area of a circle (π * r²).
  • Call with r = 4.0 in main and print the result.

Expected output

circle area (r=4) = 50.27

Hint

  • std::f64::consts::PI.
  • mod shape { pub mod circle { ... } }.

Check your answers

Try solving it yourself first, then compare your answer with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

homework_01/Cargo.tomlTOML
[package]
name = "homework_01"
version = "0.1.0"
edition = "2021"

[dependencies]
homework_01/src/main.rsRust
//Key Points:
//- Without `pub`, functions cannot be called from outside the module.
//- Using `use module::item` shortens the path and improves readability.

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

use math::{add, mul};

fn main() {
    println!("add(3, 4) = {}", add(3, 4));
    println!("mul(3, 4) = {}", mul(3, 4));
}
homework_02/Cargo.tomlTOML
[package]
name = "homework_02"
version = "0.1.0"
edition = "2021"

[dependencies]
homework_02/src/main.rsRust
//Key Points:
//- Child modules must also be exposed with `pub mod` to be accessible outside the parent.
//- Call it using a tree path like `shape::circle::area`.

mod shape {
    pub mod circle {
        use std::f64::consts::PI;

        pub fn area(r: f64) -> f64 {
            PI * r * r
        }
    }
}

fn main() {
    let r = 4.0;
    println!("circle area (r={}) = {:.2}", r as i32, shape::circle::area(r));
}

Learn this lesson with a mentor

Work through the Rust track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now