Intermediate · Modules, crates, traits and generics · topic 27 of 50 · 30 min
Packages, crates and modules
Explanation
Why organise code?
A 50-line program fits in main.rs. A 50,000-line program does not. You need to split code into parts with clear names, hide details that other parts should not touch, and reuse code written by others. Rust gives you three levels for this.
| Term | What it is | Example |
|---|---|---|
| Package | A folder with a Cargo.toml; built by cargo | the shop project |
| Crate | One unit the compiler builds: a binary or a library | src/main.rs (binary), src/lib.rs (library) |
| Module | A named section inside a crate | mod billing, mod inventory |
A binary crate has a main function and becomes a program you can run. A library crate has no main; it provides code for other crates to use. A package can have one library and several binaries. The library's root file is src/lib.rs, and the binary's root is src/main.rs.
Modules and privacy
mod billing {
pub fn total_with_gst(amount: u32) -> u32 {
amount + gst(amount)
}
fn gst(amount: u32) -> u32 { // private: only usable inside billing
amount * 18 / 100
}
}
fn main() {
println!("{}", billing::total_with_gst(1000)); // 1180
// billing::gst(1000); // error[E0603]: function `gst` is private
}Privacy is a design tool. The billing module promises one public function; it can change how gst is calculated inside without breaking anyone. Struct fields are private by default too, even if the struct is pub; mark each field pub that outsiders may use.
Modules in separate files
src/
├── main.rs // mod billing; mod inventory;
├── billing.rs // the contents of mod billing
└── inventory/
├── mod.rs // (older style) or use inventory.rs next to the folder
└── stock.rs // mod stock; declared inside inventoryPaths and use
A path names an item: crate::billing::total_with_gst starts at the crate root; super:: means the parent module; self:: the current one. use creates a shortcut so you do not repeat long paths.
use std::collections::HashMap;
use crate::billing::total_with_gst;
use std::io::{self, Write}; // several items at once
fn report() {
let mut m: HashMap<&str, u32> = HashMap::new();
m.insert("bill", total_with_gst(500));
}Using crates from crates.io
crates.io is the public registry of Rust libraries. To use one, add it to Cargo.toml, usually with cargo add, then build. cargo downloads it and its own dependencies.
cargo add rand
cargo add serde --features derive[dependencies]
rand = "0.9"
serde = { version = "1", features = ["derive"] }Semver means versions look like MAJOR.MINOR.PATCH. A MAJOR change may break your code; MINOR and PATCH should not. "1.2" in Cargo.toml means "at least 1.2.0 and below 2.0.0". Cargo.lock records the exact versions chosen so every build is the same; commit it for applications.
Common mistakes
- Forgetting pub: "function is private" (E0603). Add pub to the item, and to the module if it is nested.
- Creating billing.rs but forgetting mod billing; in main.rs or lib.rs. Files are not modules until declared.
- Making every field pub. Keep fields private and expose methods, so you can change the inside later.
- Using a crate without adding it to Cargo.toml: "unresolved import". Run cargo add.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Packages, crates and modules · 1/7
As programs grow, you split them. Rust has three levels: packages, crates and modules.
Prefer a video by a teacher? Find free videos on “rust modules crates packages pub use explained” on YouTube ↗
In real life: Structuring a billing app
A small GST billing app for shops grows from one file into a package: src/lib.rs holds modules invoice, tax and customer; src/main.rs is a thin command-line front end; tests import the library. Later the same library is reused by a web front end in another binary, without copying any code.
// src/lib.rs
pub mod tax {
pub fn gst(amount: u64, rate_percent: u64) -> u64 {
amount * rate_percent / 100
}
}
// src/main.rs
use shop::tax; // "shop" is the package name from Cargo.toml
fn main() {
println!("GST: {}", tax::gst(2_000, 12)); // GST: 240
}Practice (0/3 done)
Why does this fail? mod shop { fn open() {} } fn main() { shop::open(); }
In Cargo.toml, serde = "1.0" allows which versions?
Turn a single-file project into modules: create src/lib.rs with pub mod marks (with pub fn average(&[u32]) -> f64 and a private helper) and pub mod report. Use them from src/main.rs. Then add the rand crate with cargo add and print a random roll number.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Traits: shared behaviourModules, crates, traits and generics · same module
- Generics and trait boundsModules, crates, traits and generics · same module
- Trait objects and dynamic dispatchModules, crates, traits and generics · same module
- Integration tests and doc testsTesting, tooling and unsafe Rust
- Your first program and Go modulesGo Programming (backend and cloud) · Getting started with Go
- Tooling: rustfmt, Clippy, docs and editionsTesting, tooling and unsafe Rust
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is a crate?
Answer: A unit the compiler builds: a binary or a library
Crates are Rust's compilation units.
What is the default visibility of items in a module?
Answer: Private
Items are private unless marked pub.
What does super:: refer to in a path?
Answer: The parent module
super is the parent of the current module.
Why commit Cargo.lock for an application?
Answer: It pins exact dependency versions so builds are repeatable
Cargo.lock records the exact versions used.
You wrote src/tax.rs but get "unresolved module". What is missing?
Answer: mod tax; in main.rs or lib.rs
A file becomes a module only when declared with mod.
What is Cargo.toml?Related
Answer: The project settings: name, version, edition and dependencies
Cargo.toml describes the project and lists the crates it depends on.
Why might a binary-only crate be hard to integration test?Related
Answer: Integration tests import a library crate; put the logic in lib.rs
Keep main.rs thin and put logic in the library.
Which file marks a folder as a Go module?Related
Answer: go.mod
go.mod holds the module path and Go version and marks the module root.
What does cargo-audit check?Related
Answer: Dependencies against a database of known vulnerabilities
It uses the RustSec advisory database.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Modules, crates, traits and generics” before its exam: Traits: shared behaviour, Generics and trait bounds, Trait objects and dynamic dispatch.
- Module 7 exam: Modules, crates, traits and generics · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Closures, iterators and smart pointers. 50 topics left in the course before the final exam.