Modules, Packages & Cargo
Rust module system, visibility, use statements, crate structure, and Cargo workflow
Rust’s module system controls privacy and organization. The default is private — you must explicitly mark items as pub to expose them. Cargo is the build system and package manager.
Module basics
// mod declares a module (can be inline or in a separate file)
mod network {
// Private by default
fn connect() -> bool { true }
// pub makes it accessible outside this module
pub fn send(data: &str) -> bool {
if connect() {
println!("Sending: {}", data);
true
} else {
false
}
}
// pub(crate) — visible within the crate, not externally
pub(crate) fn internal_log(msg: &str) {
println!("[internal] {}", msg);
}
// Nested module
pub mod protocol {
pub fn parse(header: &str) -> String {
format!("Parsed: {}", header)
}
}
}
// Access module items
network::send("hello");
network::protocol::parse("HTTP");
// network::connect(); // ERROR: private
use — bringing items into scope
// Single item
use std::collections::HashMap;
// Multiple items
use std::fs::{self, File}; // fs module + File type
// Glob (use sparingly)
use std::io::prelude::*;
// Aliasing with as
use std::io::Result as IoResult;
// Re-exporting with pub use
pub use crate::network::protocol::parse;File-based modules
Rust maps modules to the filesystem:
src/
├── main.rs // crate root for binary
├── lib.rs // crate root for library
└── network/
├── mod.rs // declares `mod network`
├── server.rs // network::server
└── protocol.rs // network::protocol
// In src/lib.rs (crate root)
pub mod network; // tells Rust to look in src/network/mod.rs
// In src/network/mod.rs
pub mod server; // src/network/server.rs
pub mod protocol; // src/network/protocol.rs
// In src/network/server.rs
pub fn start() { /* ... */ }Modern Rust (2018+): You can use
network.rsinstead ofnetwork/mod.rsfor the top-level module file. Themod.rsconvention is still common but both work.
Visibility cheat sheet
fn private() {} // private — module only
pub fn public() {} // public — anyone
pub(crate) fn crate_wide() {} // crate only
pub(super) fn parent_only() {} // parent module only
pub(in path) fn specific() {} // specific path only
| Visibility | Accessible from |
|---|---|
| (no pub) | Same module and descendants |
pub(super) |
Parent module |
pub(crate) |
Entire crate |
pub |
Everyone (including external crates) |
pub(in path) |
Specified path |
Cargo — the build system
Project structure
myproject/
├── Cargo.toml // project metadata & dependencies
├── Cargo.lock // locked dependency versions (git this)
├── src/
│ ├── main.rs // binary entry point
│ └── lib.rs // library (optional)
├── tests/ // integration tests
│ └── integration_test.rs
├── benches/ // benchmarks
└── examples/ // examples
└── demo.rs
Cargo.toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
[dev-dependencies]
assert_cmd = "2"
proptest = "1"
[profile.release]
opt-level = 3
lto = trueCommon commands
cargo new myproject # create new project
cargo new --lib mylib # create library project
cargo init # init in existing directory
cargo build # compile (debug mode)
cargo build --release # compile with optimizations
cargo check # type-check only (much faster)
cargo run # build and run
cargo run -- arg1 arg2 # run with arguments
cargo test # run all tests
cargo test test_name # run specific test
cargo test -- --nocapture # show println! output
cargo fmt # format code
cargo clippy # lint code
cargo doc --open # generate and open docs
cargo add serde # add dependency
cargo add tokio --features full # add with features
cargo update # update dependencies
cargo tree # show dependency treeWorkspace — multi-crate projects
# In workspace root Cargo.toml
[workspace]
members = [
"app",
"core",
"api",
]workspace/
├── Cargo.toml # workspace root
├── app/
│ └── Cargo.toml # binary crate
├── core/
│ └── Cargo.toml # library crate
└── api/
└── Cargo.toml # library crate
Testing
// Unit tests (in same file, marked with #[cfg(test)])
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_error() {
let result = divide(10, 0);
assert!(result.is_err());
}
#[test]
#[should_panic(expected = "division by zero")]
fn test_panic() {
divide(10, 0);
}
#[test]
fn test_option() -> Option<()> {
let val = parse("42")?;
assert_eq!(val, 42);
Some(())
}
}Integration tests go in tests/ directory — each file is a separate crate:
// tests/integration_test.rs
use myproject::add;
#[test]
fn test_integration() {
assert_eq!(add(2, 3), 5);
}Feature flags
# Cargo.toml
[features]
default = ["json"]
json = ["serde_json"]
full = ["json", "tls"]
[dependencies]
serde_json = { version = "1.0", optional = true }// Conditional compilation
#[cfg(feature = "json")]
pub fn parse_json(input: &str) -> Value {
serde_json::from_str(input).unwrap()
}Common Cargo.toml patterns
# Specific version
serde = "1.0"
# With features
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
# Git dependency
mylib = { git = "https://github.com/user/mylib", branch = "main" }
# Local path dependency
common = { path = "../common" }
# Development-only dependency
[dev-dependencies]
criterion = "0.5"