Rust Cargo and Modules: Project Structure and Dependencies
Cargo is Rust’s build system, package manager, and project orchestrator — all in one. It handles building, testing, running, documenting, and publishing your Rust code. Combined with Rust’s module system, Cargo provides a complete toolchain for organizing projects of any size, from single-file scripts to multi-crate workspaces.
Getting Started with Cargo
Creating a New Project
# Binary project
cargo new my_project
cd my_project
# Library project
cargo new --lib my_libraryThis creates the standard structure:
my_project/
├── Cargo.toml # Project manifest
├── Cargo.lock # Dependency lock file
└── src/
└── main.rs # Entry point (or lib.rs for libraries)Basic Commands
cargo build # Build in debug mode
cargo build --release # Build optimized
cargo run # Build and run
cargo check # Check for errors without producing binaries
cargo test # Run tests
cargo clippy # Lint with Clippy
cargo fmt # Format code
cargo doc --open # Build and open documentationCargo.toml
The manifest file defines metadata, dependencies, and build configuration:
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
description = "A sample Rust project"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = "0.12"
[dev-dependencies]
criterion = "0.5"
[build-dependencies]
cc = "1.0"
[features]
default = ["std"]
std = []
[profile.release]
opt-level = 3
lto = true
codegen-units = 1Dependencies
Adding Dependencies
cargo add serde # Latest version
cargo add tokio --features full # With features
cargo add --dev criterion # Dev dependency only
cargo add --build cc # Build dependency
cargo remove unused_dep # Remove dependency
cargo update # Update lock fileVersion Specifiers
[dependencies]
exact = "=1.2.3" # Exactly 1.2.3
compatible = "1.2.3" # ^1.2.3 — >=1.2.3, <2.0.0
pessimistic = "~1.2.3" # >=1.2.3, <1.3.0
wildcard = "*" # Any version (not recommended)
git = { git = "https://github.com/user/repo" }
path = { path = "../local_crate" }Modules
Rust’s module system controls visibility and organization of code:
// src/main.rs or src/lib.rs
mod math; // Declares the `math` module (looks for math.rs or math/mod.rs)
mod utils; // Declares the `utils` module
use crate::math::add; // Bring `add` into scope
use crate::utils::format; // Bring `format` into scope
fn main() {
let result = add(2, 3);
println!("{}", format(result));
---Module File Structure
src/
├── main.rs
├── math.rs // module `math`
├── utils/
│ ├── mod.rs // module `utils`
│ ├── format.rs // submodule `utils::format`
│ └── io.rs // submodule `utils::io`// src/math.rs — module `math`
pub fn add(a: i32, b: i32) -> i32 {
a + b
---
fn private_helper() { // Not visible outside the module
// ...
---// src/utils/mod.rs — module `utils`
pub mod format;
pub mod io;// src/utils/format.rs
pub fn format(value: i32) -> String {
format!("Value: {}", value)
---Visibility
pub fn public_function() {} // Visible everywhere
fn private_function() {} // Visible only in this module
pub(crate) fn crate_visible() {} // Visible within the crate
pub(super) fn parent_visible() {} // Visible in parent module
pub(in crate::utils) fn restricted() {} // Visible in specific module
Re-exports
// src/lib.rs
mod internal;
// Re-export to create a public API different from internal structure
pub use internal::parser::parse;
pub use internal::types::*;
mod internal {
pub mod parser {
pub fn parse(input: &str) -> i32 {
input.parse().unwrap()
}
}
pub mod types {
pub struct Config {
pub timeout: u64,
}
}
---Crates
A crate is the smallest unit of compilation in Rust. Every crate has a root file (main.rs or lib.rs).
Binary Crates
// src/main.rs
fn main() {
println!("Hello, world!");
---Library Crates
// src/lib.rs — exposes public API
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
---
// Used by binaries or other crates
Mixing Binaries and Libraries
my_project/
├── src/
│ ├── lib.rs # Library root
│ ├── main.rs # Binary root (uses library)
│ └── bin/ # Additional binaries
│ ├── tool1.rs
│ └── tool2.rs// src/main.rs — binary uses the library
use my_project::greet;
fn main() {
println!("{}", greet("World"));
---Workspaces
Workspaces let you manage multiple related crates in a single project:
my_workspace/
├── Cargo.toml # Workspace manifest
├── crates/
│ ├── core/
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ ├── cli/
│ │ ├── Cargo.toml
│ │ └── src/main.rs
│ └── web/
│ ├── Cargo.toml
│ └── src/lib.rs# Cargo.toml (workspace root)
[workspace]
members = [
"crates/core",
"crates/cli",
"crates/web",
]
resolver = "2"# crates/cli/Cargo.toml
[dependencies]
core = { path = "../core" }
web = { path = "../web" }Workspace commands operate on all members:
cargo build --workspace # Build all crates
cargo test --workspace # Test all crates
cargo check -p core # Check specific cratePublishing to crates.io
Prepare for Publishing
# Login
cargo login
# Verify package
cargo package --list # See what will be included
cargo publish --dry-run # Test without publishingMetadata
[package]
name = "my_crate"
version = "0.1.0"
edition = "2021"
description = "A useful Rust crate"
license = "MIT OR Apache-2.0"
repository = "https://github.com/user/my_crate"
documentation = "https://docs.rs/my_crate"
keywords = ["utility", "example"]
categories = ["development-tools"]
readme = "README.md"Publishing
cargo publish # Publish to crates.io
cargo yank --vers 0.1.0 # Prevent new downloads of a versionBest Practices
- Use the 2021 edition for all new projects
- Organize code by feature, not by technical layer (prefer
features/auth.rsoverfeatures/controllers/mod.rs) - Keep lib.rs minimal — re-export from submodules rather than defining everything there
- Use workspace for multi-crate projects — it enables shared dependencies and coordinated builds
- Pin dependencies with
cargo updateand commitCargo.lockfor applications - Run
cargo clippyandcargo fmtin CI - Write documentation with
///doc comments —cargo docgenerates API docs - Use
#[warn(unused_crate_dependencies)]to catch unused dependencies - Publish only library crates — keep applications private unless you have a reason
Conclusion
Cargo and the module system are central to Rust development. Use Cargo for project creation, dependency management, building, and testing. Structure code into modules for organization, combine crates into workspaces for multi-package projects, and publish to crates.io to share your work. Together, they provide a productive, standards-based development experience.
Related: See our Rust traits and generics guide and Rust error handling.
Workspace Management
Cargo workspaces manage multiple related crates in a single repository. A root Cargo.toml with [workspace] defines member crates, each in their own subdirectory with independent Cargo.toml files. Workspace members share a single Cargo.lock file and target directory, ensuring consistent dependency versions. This pattern is essential for large projects split into library and binary crates, or for projects with multiple interdependent components.
Build Scripts and Procedural Macros
Build scripts (build.rs) execute before crate compilation, generating code, compiling native libraries, or generating bindings. Procedural macros operate on the AST at compile time, enabling custom derive macros, attribute macros, and function-like macros. Both features extend Cargo’s build system beyond simple compilation, but should be used judiciously due to their complexity.
Rust in Production: Case Studies
Rust has been adopted in production by major technology companies for performance-critical and safety-critical systems. Mozilla uses Rust in the Firefox browser engine (Servo/Quantum) for improved memory safety and performance in CSS layout and rendering. Dropbox rewrote their sync engine core in Rust, achieving significant performance improvements and eliminating entire classes of bugs. Cloudflare uses Rust for edge computing infrastructure, including their Pingora HTTP proxy that handles 10% of the world’s internet traffic. Figma rewrote their multiplayer editing engine in Rust, achieving predictable performance and eliminating crashes. Discord used Rust for their read states service, handling millions of concurrent connections with minimal resources. These case studies demonstrate Rust’s value in systems where performance, reliability, and memory safety are critical. The common pattern: rewrite performance-critical components in Rust while maintaining the rest of the system in the original language, proving that incremental adoption works in practice.
Embedded Systems Programming
Rust is gaining traction in embedded systems for its memory safety guarantees without a garbage collector or runtime. The cortex-m-quickstart template provides a starting point for ARM Cortex-M microcontrollers. The RTIC framework provides real-time interrupt-driven concurrency. Embassy offers an async embedded runtime with USB, networking, and BLE support. Rust’s type system prevents common embedded errors like misconfiguring peripherals, incorrect register access, and data races between interrupt handlers and main code. The embedded ecosystem continues to mature with HAL implementations for major microcontroller families from STM, Nordic, and Espressif.
FAQ
What background knowledge is recommended? A basic understanding of programming fundamentals will help, but many concepts are explained from first principles.
How can I practice these skills? Start with small projects, contribute to open source, and build a portfolio. Consistent practice is more effective than occasional deep dives.
What tools do I need to get started? Most topics require only a text editor, the relevant runtime, and package manager. Specific tool recommendations are included throughout.