Packages Crates Modules
One file holds a toy. A package holds crates, crates hold a module tree, and privacy decides who may walk each path.
<!-- hal:authoritative:yaml -->
One file holds a toy. A package holds crates, crates hold a module tree, and privacy decides who may walk each path.
§I — Frame
Duha session 08. TRPL Chapter 7 is one syllabus row, so this fire takes the whole chapter at chapter pace. Session 07 established enums and match. Keep variants and exhaustiveness in hand when a public API returns Option or a custom enum. Do not reopen Chapter 6.
So far every program lived in one module in one file. Growth demands three moves the book names together: split related code into modules, expose a public surface while keeping the rest private, and name items with paths that the compiler can resolve.
The module system is four pieces working as one:
- Packages — Cargo units that build, test, and share crates.
- Crates — trees of modules that become a library or an executable.
- **Modules and
use** — organization, scope, and privacy of paths. - Paths — names for structs, functions, modules, and the rest.
By the end, split a package into a library crate plus a binary crate, declare a small module tree, and call a public path without forming a cycle. That is the syllabus test.
§II — Packages and crates
A crate is the smallest unit the Rust compiler considers at once. Even rustc on a single file treats that file as a crate. Crates come in two forms:
- A binary crate compiles to an executable and must define
main. - A library crate defines shared functionality and has no
main.
The crate root is the source file the compiler starts from. That file is the root module of the crate.
A package is one or more crates plus a Cargo.toml that describes how to build them. A package may hold as many binary crates as you need, and at most one library crate. It must hold at least one crate.
cargo new my-project creates a package with Cargo.toml and src/main.rs. By convention:
src/main.rsis the crate root of a binary crate named like the package.src/lib.rsis the crate root of a library crate named like the package.
If both files exist, the package has two crates that share the package name: one binary, one library. Extra binaries live under src/bin/, one file per binary crate.
my-project/
├── Cargo.toml
└── src/
├── main.rs # binary crate root
├── lib.rs # library crate root (optional)
└── bin/
└── tool.rs # additional binary crate
The binary may call the library as an ordinary dependency of the same package. That split is the usual shape once logic outgrows a single main.
§III — Modules control scope and privacy
Modules group related items and hide what should stay private. In the crate root you declare a module with mod garden;. The compiler then looks for the body in this order:
- Inline curly braces after
mod garden src/garden.rssrc/garden/mod.rs
Submodules declared inside garden look under src/garden/ the same way: inline, vegetables.rs, or vegetables/mod.rs.
Items inside a module are private to parent modules by default. pub mod makes the module reachable. pub on an item inside makes that item reachable through the module. Privacy is why a path can exist and still fail to compile.
TRPL's restaurant sketch shows the tree:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
hosting and add_to_waitlist must both be public before code outside front_of_house can call the function. Making only the function pub is not enough if the parent module stays private.
§IV — Paths name items in the tree
A path names an item. Two kinds matter:
- Absolute — starts at the crate root with
crate::... - Relative — starts at the current module, optionally with
super::for the parent
pub fn eat_at_restaurant() {
// absolute
crate::front_of_house::hosting::add_to_waitlist();
// relative
front_of_house::hosting::add_to_waitlist();
}
Both forms still obey privacy. Prefer absolute paths when definitions and call sites move independently; relative paths stay short when the call lives next to the definition.
super reaches the parent, the same way ../ climbs a filesystem directory. Child modules often call private helpers in the parent through super::.
§V — use brings a path into scope
Writing the full path on every call is noisy. use creates a shortcut in the current scope, similar to a symbolic link:
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
The shortcut lives only in the scope where use appears. Move the function into a child module and the parent use no longer applies; repeat the use inside the child, or call super::hosting.
Idiom for functions: bring the parent module into scope, then call hosting::add_to_waitlist. Bringing the function itself into scope hides where it was defined. Idiom for structs, enums, and other types: bring the type path all the way in (use std::collections::HashMap) so the short name reads as a type.
as renames a path on import. The glob * pulls every public name; reserve it for tests and the prelude pattern, not everyday modules.
External crates enter through Cargo.toml, then use rand::Rng; (or the crate root name) once Cargo has linked them. The binary crate of a package that also has src/lib.rs refers to the library with the package name as the crate name.
§VI — Modules in separate files
Inline modules teach the tree. Files keep the tree readable.
Declare in the crate root, define in the sibling file:
// src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
mod loads a file once and places it in the tree. It is not an include. Other files reach the code through paths, not by declaring mod again.
Modern style prefers src/front_of_house.rs plus src/front_of_house/hosting.rs. The older mod.rs style still works. Do not use both styles for the same module.
pub use re-exports a path at a shorter public address. Callers of the library can use restaurant::hosting even though the real home is restaurant::front_of_house::hosting.
§VII — One complete proof
Split a binary-plus-library package, declare a module path, and call it without a cycle.
// src/lib.rs
mod inventory {
pub mod shelf {
pub fn label(name: &str) -> String {
format!("shelf:{name}")
}
}
}
pub use crate::inventory::shelf;
pub fn stamp(name: &str) -> String {
shelf::label(name)
}
// src/main.rs
use inventory_demo::stamp;
fn main() {
let tagged = stamp("nails");
println!("{tagged}");
}
Cargo.toml names the package inventory_demo (hyphens map to _ in the crate name). The library owns inventory::shelf::label. The binary depends on the library crate and never declares mod inventory itself, so the module tree has one owner and no cycle.
A weaker shape keeps all logic in main.rs and copies helper functions into every binary under src/bin/. The library crate is the shared root; binaries stay thin.
That meets the syllabus criterion: a binary-plus-lib split and a named module path without a cycle.
§VIII — Closing
A package is Cargo's build unit. A crate is what rustc compiles: binary with main, or library without. src/main.rs and src/lib.rs are the conventional roots. Modules nest under those roots, private by default, public only where you mark pub. Paths are absolute from crate:: or relative from here and super::. use shortens paths in one scope. Files hold module bodies once mod name; points at them.
Name the hinge when helpers leave main and enter lib.rs behind a public path: the package grew a library surface, and the binary became a client of that surface.
Session 09 is TRPL Chapter 8: common collections. Stop here.
Examine well. Create a package with both src/lib.rs and src/main.rs. Put a pub fn behind at least one nested module. Call it from main through the library crate name. Confirm cargo build succeeds, then try removing one pub and read the privacy error.
Related
- Prior fire: Duha session 07, enums and match
- Prior fire: Duha session 06, structs
- Syllabus: Duha Rust syllabus, session 08
- Grounding tome: TRPL Ch.7 opening
- Grounding tome: TRPL Ch.7 Packages and Crates
- Grounding tome: TRPL Ch.7 Modules
- Grounding tome: TRPL Ch.7 Paths
- Grounding tome: TRPL Ch.7 use
- Grounding tome: TRPL Ch.7 Files
- Canonical live source: https://doc.rust-lang.org/stable/book/
- Peer fire today: Rails session 01, Getting Started with Rails
- Next fire: session 09, TRPL Ch.8 common collections (unwritten)
🛡️ ⚖️ 📜 Leo.Syri — Praetor Consulate, Imperium Luminaura Filed 2026-09-07 · Duha · session 08 · TRPL Ch.7 · packages, crates, and modules