On this page
Modules, crates, and the package system
Rust's module system
Rust organizes code with an explicit, hierarchical module system. Modules control the privacy of items (functions, structs, enums, traits) and organize the namespace.
The hierarchy: packages, crates, and modules
Package
├── Cargo.toml
└── Crates
├── Binary crate (src/main.rs) — produces an executable
└── Library crate (src/lib.rs) — produces a reusable library
└── Modules
├── module::submodule::item
└── ...A package can have one or more crates. A crate has a "crate root" (main.rs or lib.rs). Modules organize code within a crate.
Inline modules
// src/main.rs
mod fruits {
// pub: visible outside the module
pub struct Fruit {
pub name: String,
calories: u32, // private
}
impl Fruit {
pub fn new(name: &str, calories: u32) -> Self {
Fruit {
name: name.to_string(),
calories,
}
}
pub fn calories(&self) -> u32 {
self.calories
}
}
pub mod tropical {
use super::Fruit; // super: parent module
pub fn mango() -> Fruit {
Fruit::new("mango", 60)
}
pub fn papaya() -> Fruit {
Fruit::new("papaya", 43)
}
}
}
fn main() {
// Absolute path from the crate root
let f = fruits::Fruit::new("apple", 52);
println!("{}: {} cal", f.name, f.calories());
// Access to submodule
let m = fruits::tropical::mango();
println!("{}: {} cal", m.name, m.calories());
}Modules in separate files
For large projects, each module lives in its own file:
src/
├── main.rs
├── math.rs ← mod math;
└── math/
├── mod.rs (alternative: or use math.rs)
└── advanced.rs ← pub mod advanced;// src/main.rs
mod math; // Looks for src/math.rs or src/math/mod.rs
use math::add;
use math::advanced::factorial;
fn main() {
println!("3 + 4 = {}", add(3.0, 4.0));
println!("5! = {}", factorial(5));
}// src/math.rs
pub mod advanced; // Looks for src/math/advanced.rs
pub fn add(a: f64, b: f64) -> f64 { a + b }
pub fn subtract(a: f64, b: f64) -> f64 { a - b }
pub fn multiply(a: f64, b: f64) -> f64 { a * b }// src/math/advanced.rs
pub fn factorial(n: u64) -> u64 {
(1..=n).product()
}
pub fn combinatorial(n: u64, r: u64) -> u64 {
if r > n { return 0; }
factorial(n) / (factorial(r) * factorial(n - r))
}The `use` keyword: importing paths
mod geometry {
pub mod shapes {
pub struct Circle(pub f64);
pub struct Square(pub f64);
}
}
// Import a specific item
use geometry::shapes::Circle;
// Import multiple items with curly braces
use geometry::shapes::{Circle as C, Square};
// Import everything (glob import) — use carefully
use geometry::shapes::*;
fn main() {
let c = Circle(5.0);
let s = Square(4.0);
println!("Radius: {}, Side: {}", c.0, s.0);
}Re-exporting with `pub use`
// src/lib.rs
mod implementation {
pub struct Connection {
pub url: String,
}
impl Connection {
pub fn new(url: &str) -> Self {
Connection { url: url.to_string() }
}
}
}
// Re-export: users can use my_crate::Connection
// instead of my_crate::implementation::Connection
pub use implementation::Connection;Cargo.toml: dependencies
The Cargo.toml file is the project manifest:
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
authors = ["David Morales <[email protected]>"]
description = "My Rust application"
[dependencies]
# Semantic versioning
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Caret: ^1.0 = >=1.0.0, <2.0.0
tokio = { version = "^1.0", features = ["full"] }
# Tilde: ~1.2 = >=1.2.0, <1.3.0
log = "~0.4"
# Exact version
chrono = "=0.4.38"
# From git
# my-crate = { git = "https://github.com/user/my-crate" }
# From local path (for workspaces)
# my-util = { path = "../my-util" }
[dev-dependencies]
# Only for tests
pretty_assertions = "1"
[build-dependencies]
# For build scripts (build.rs)
cc = "1"
[profile.release]
opt-level = 3
lto = trueWorkspaces: multiple crates in one repository
For large projects with multiple related crates:
# Root Cargo.toml (workspace)
[workspace]
members = [
"core",
"api-server",
"cli",
"utils",
]
resolver = "2"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }# core/Cargo.toml
[package]
name = "my-core"
version = "0.1.0"
edition = "2021"
[dependencies]
serde.workspace = true # Uses the workspace versionEssential crates from the ecosystem
| Crate | Purpose |
|---|---|
serde + serde_json |
Serialization/deserialization |
tokio |
Async runtime |
reqwest |
HTTP client |
clap |
CLI argument parsing |
thiserror |
Error types with derive |
anyhow |
Application error handling |
log + env_logger |
Logging |
chrono |
Dates and times |
regex |
Regular expressions |
rand |
Random numbers |
Adding and using a real dependency
cargo add serde --features derive
cargo add serde_jsonuse serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Product {
id: u32,
name: String,
price: f64,
available: bool,
}
fn main() {
let product = Product {
id: 1,
name: String::from("Mechanical Keyboard"),
price: 89.99,
available: true,
};
// Serialize to JSON
let json = serde_json::to_string_pretty(&product).unwrap();
println!("{json}");
// Deserialize from JSON
let json_input = r#"{"id":2,"name":"Mouse","price":29.99,"available":false}"#;
let product2: Product = serde_json::from_str(json_input).unwrap();
println!("{:?}", product2);
}With the module system mastered, we move on to one of Rust's most elegant features: closures and iterators — the functional way to transform data.
pub(crate) vs pub vs pub(super)
Rust has granular visibility: pub makes the item public to everyone, pub(crate) limits it to the current crate, pub(super) limits it to the parent module, and no pub means private to the current module. Prefer pub(crate) over pub when the item is not part of the library's public API.
crates.io and Cargo.lock
Cargo.lock guarantees reproducible builds — it stores the exact versions of all dependencies. For binary projects you should commit Cargo.lock to the repository. For libraries, generally do NOT commit it (consumers of the library will resolve versions themselves).
// src/lib.rs — library crate root
// Inline modules
pub mod math {
pub fn add(a: f64, b: f64) -> f64 { a + b }
pub fn subtract(a: f64, b: f64) -> f64 { a - b }
// Submodule
pub mod advanced {
pub fn factorial(n: u64) -> u64 {
(1..=n).product()
}
pub fn fibonacci(n: u32) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
}
}
// Private module (no pub)
mod internal {
pub(super) fn helper() -> &'static str { "internal help" }
}
pub fn use_internal() -> &'static str {
internal::helper()
}
// Re-export with pub use
pub use math::advanced::factorial;
Sign in to track your progress