Modules & Crates

Reviewed & published by Brayan K

Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and it organises growing codebases with modules for namespacing and crates for distribution.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll declare modules with mod, control visibility with pub, navigate with paths, shorten names with use, and pull in external crates.

What You'll Learn in This Lesson

1️⃣ Modules, Visibility, and Paths

A mod block creates a namespace. Items inside are private by default; add pub to expose them. You reach an item by its path, joining names with :: — for example math::advanced::square.

// mod creates a module — a named namespace that groups related code.
// Items are PRIVATE by default; mark them pub to expose them.
mod math {
    // pub makes this function callable from outside the module.
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    // Modules can nest. This inner module is public too.
    pub mod advanced {
        pub fn square(n: i32) -> i32 {
            n * n
        }
    }

    // Private helper — only usable inside this module.
    fn secret() -> i32 { 42 }

    pub fn reveal() -> i32 { secret() }  // ok: same module can call it
}

fn main() {
    // Reach items with paths using :: between names.
    println!("add: {}", math::add(2, 3));               // -> add: 5
    println!("square: {}", math::advanced::square(4));  // -> square: 16
    println!("reveal: {}", math::reveal());             // -> reveal: 42
}

Notice secret is private, so main can't call it — but reveal, which lives in the same module, can. That's encapsulation: the module hides its internals.

2️⃣ The use Keyword and Crates

Writing long paths repeatedly is tedious, so use brings a path into scope under a short name. The same mechanism imports the standard library (the std crate) and external crates you add in Cargo.toml — Cargo downloads and builds them for you.

mod shapes {
    pub struct Circle {
        pub radius: f64,
    }

    impl Circle {
        pub fn new(radius: f64) -> Circle {
            Circle { radius }
        }
        pub fn area(&self) -> f64 {
            3.14159 * self.radius * self.radius
        }
    }
}

// "use" brings a path into scope so you can write a short name.
use shapes::Circle;

// External crates from crates.io are added in Cargo.toml, then imported with use.
// e.g. add  rand = "0.8"  to [dependencies], then:  use rand::Rng;

fn main() {
    // Thanks to the use above, we can say Circle instead of shapes::Circle.
    let c = Circle::new(2.0);
    println!("area = {:.2}", c.area());   // -> area = 12.57

    // The standard library is the std crate, used the same way.
    use std::cmp::max;
    println!("max = {}", max(3, 9));      // -> max = 9
}

After use shapes::Circle; we wrote just Circle. To pull in a crate like rand, you'd add it under [dependencies] in Cargo.toml and then use it the same way.

Your turn. Fill in the blanks marked ___, then run it.

// 🎯 YOUR TURN — fix the visibility and the path, then run it.
mod greetings {
    // 1) Make this function callable from main by adding the right keyword.
    ___ fn hello(name: &str) -> String {     // 👉 the keyword that exposes it
        format!("Hello, {}!", name)
    }
}

// 2) Bring greetings::hello into scope so we can call it by its short name.
use greetings::___;                          // 👉 the function name to import

fn main() {
    println!("{}", hello("Sam"));   // expected: Hello, Sam!
}

// ✅ Expected output:
//    Hello, Sam!

Mini-Challenge: A Temperature Module

Build a module with two public conversion functions and call them via use. Run it with cargo run.

// 🎯 MINI-CHALLENGE: a small temperature module.
//
// 1. Create a module "temperature" with two PUBLIC functions:
//      - c_to_f(c: f64) -> f64  returning c * 9.0 / 5.0 + 32.0
//      - f_to_c(f: f64) -> f64  returning (f - 32.0) * 5.0 / 9.0
// 2. Use a "use" statement to bring both into scope (use temperature::{c_to_f, f_to_c};).
// 3. In main, print c_to_f(100.0) and f_to_c(32.0).
//
// ✅ Example output:
//    100C = 212F
//    32F = 0C

fn main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Modules & Crates

GoalRust Syntax
Declare modulemod math { }
Make publicpub fn add() { }
Path to itemmath::add(2, 3)
Importuse math::add;
Group importuse std::cmp::{min, max};
Add craterand = "0.8" (Cargo.toml)

🎉 Lesson Complete!

Practice quiz

Which keyword declares a module?

  • module
  • namespace
  • mod
  • pkg

Answer: mod. mod creates a named module that groups related code.

By default, items inside a module are:

  • Private
  • Public
  • Mutable
  • Static

Answer: Private. Items are private by default; you expose them with pub.

Which keyword makes an item accessible outside its module?

  • open
  • export
  • extern
  • pub

Answer: pub. pub marks an item as public so callers outside the module can use it.

What separates names in a Rust path?

  • A dot .
  • Two colons ::
  • A slash /
  • An arrow ->

Answer: Two colons ::. Paths join names with ::, like math::advanced::square.

What does the use keyword do?

  • Brings a path into scope under a short name
  • Copies code into the file
  • Declares a module
  • Runs a function

Answer: Brings a path into scope under a short name. use creates a local shortcut to a name that exists elsewhere.

What is a crate?

  • A single function
  • A type of variable
  • The unit of compilation and distribution
  • A trait

Answer: The unit of compilation and distribution. A crate is a whole library or binary; modules organize code inside it.

Can a private function be called from elsewhere in the same module?

  • No, never
  • Yes
  • Only with unsafe
  • Only if it is static

Answer: Yes. Code in the same module can call its own private items, like reveal calling secret.

Does a pub struct automatically make all its fields public?

  • Yes
  • Only numeric fields
  • Only the first field
  • No, each field needs pub too

Answer: No, each field needs pub too. A pub struct still has private fields unless each field is also marked pub.

How do you import several names in one use statement?

  • use a, b;
  • use std::cmp::{min, max};
  • use std::cmp::min+max;
  • You cannot

Answer: use std::cmp::{min, max};. Braces group imports, like use std::cmp::{min, max};.

Where do you add an external crate from crates.io?

  • In main.rs
  • With a mod statement
  • In the [dependencies] section of Cargo.toml
  • In a .gitignore

Answer: In the [dependencies] section of Cargo.toml. Add it under [dependencies] in Cargo.toml, then use it in code.

Continue this course

Frequently asked questions

What is the difference between a module and a crate?

A crate is the unit of compilation and distribution in Rust — it is a whole library or binary, like your project or a package from crates.io. A module is a namespace inside a crate that groups related items (functions, structs, other modules) and controls their visibility. So one crate contains a tree of modules. You create a binary crate with cargo new, and you organise the code inside it using mod to declare modules.

Why is everything private by default in Rust?

Privacy by default encourages good encapsulation: a module's internals stay hidden unless you deliberately expose them with pub. This means you can change private implementation details freely without breaking code outside the module, because nothing outside could depend on them. To make a function, struct, field, or module usable from outside its parent module, you add the pub keyword. This explicit opt-in keeps each module's public surface small and intentional.

What does the use keyword actually do?

use brings a path into the current scope so you can refer to an item by a shorter name instead of writing its full path every time. For example, after 'use std::collections::HashMap;' you can write HashMap rather than std::collections::HashMap. It does not move or copy any code; it is purely a convenience that creates a local shortcut to a name that already exists elsewhere. You can also import several items at once with braces, like use std::cmp::{min, max};.

How do I add and use an external crate from crates.io?

Add the crate and a version to the [dependencies] section of your Cargo.toml, for example 'rand = "0.8"'. The next time you build with cargo build or cargo run, Cargo downloads and compiles it. Then in your code you bring items into scope with use, such as 'use rand::Rng;', and call them. Cargo manages versions and transitive dependencies for you, which is a big part of why the Rust ecosystem is so pleasant to work in.