Dependency Management
Cargo.toml is the manifest file of a Rust project.
It tells Cargo:
- what this project is
- how it’s structured
- what it depends on
- how it should be built
Think of it as:
package.json(Node)pom.xml(Maven)go.mod(Go)
Why Cargo.toml Matters in Project Structure
Cargo.toml defines:
- crate identity (name, version)
- binary vs library
- dependencies between crates
- workspace layout
- features and build behavior
Without it, Rust code doesn’t exist as a project.
Basic Cargo.toml Structure
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
| Concept | Purpose |
|---|---|
| Module | Organize code inside a crate |
| Crate | Compilation unit |
| Workspace | Organize multiple crates |
Binary vs Library Crates in Cargo.toml
Binary crate (default)
[package]
name = "my_app"
- Requires
src/main.rs - Builds an executable Library crate
[package]
name = "my_lib"
[lib]
name = "my_lib"
path = "src/lib.rs"
- Produces a reusable library
- Used by other crates
Both Binary + Library
src/
├── main.rs
└── lib.rs
Cargo automatically detects both—no extra config needed.
Declaring Dependencies
Basic dependency
[dependencies]
serde = "1.0"
- Fetches from crates.io
- Uses latest compatible
1.x.x - Downloaded automatically
Using Dependencies in Modules
use serde::Serialize;
#[derive(Serialize)]
struct User {
name: String,
}
- Crate name becomes top-level path
- Works in any module
Dependency Versions (SemVer Rules)
| Version | Meaning |
|---|---|
"1.2.3" | Exact version |
"^1.2.3" | Compatible updates |
"~1.2.3" | Patch updates only |
"*" | Any version (avoid) |
serde = "^1.0"
Dev Dependencies
Used only for tests and examples.
[dev-dependencies]
tokio = { version = "1", features = ["macros"] }
Available in:
- tests
- benchmarks
- examples
Not included in release builds.
Optional Dependencies & Features
Defining features
[features]
default = ["json"]
json = ["serde"]
Optional dependency
[dependencies]
serde = { version = "1.0", optional = true }
Usage
#[cfg(feature = "json")]
fn serialize() {}
This enables conditional compilation.
Path Dependencies (Local Crates)
Used in workspaces or local development.
[dependencies]
core = { path = "../core" }
- No publishing required
- Fast iteration
- Common in monorepos
Git Dependencies
[dependencies]
my_lib = { git = "https://github.com/user/my_lib", branch = "main" }
Used when:
- dependency isn’t on crates.io
- testing unreleased features
Workspace Dependencies (Shared Versions)
Root Cargo.toml
[workspace]
members = ["app", "core"]
[workspace.dependencies]
serde = "1.0"
Member crate
[dependencies]
serde = { workspace = true }
Guarantees:
- same version everywhere
- no version conflicts
Dependency Resolution & Cargo.lock
Cargo.lock
- Exact versions of dependencies
- Ensures reproducible builds
- Shared across workspace
Rule:
- commit
Cargo.lockfor binaries - do NOT commit for libraries
Target-Specific Dependencies
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
Only compiled on Windows.
Dependency Features (Enabling APIs)
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Features:
- enable extra functionality
- reduce binary size
- control behavior
Example: Real Project Structure
my_workspace/
├── Cargo.toml
├── api/
│ └── Cargo.toml
└── shared/
└── Cargo.toml
Root Cargo.toml
[workspace]
members = ["api", "shared"]
shared/Cargo.toml
[package]
name = "shared"
[dependencies]
serde = { workspace = true }
api/Cargo.toml
[package]
name = "api"
[dependencies]
shared = { path = "../shared" }
serde = { workspace = true }
How Cargo Connects to Modules
| Layer | Controlled By |
|---|---|
| Workspace | Root Cargo.toml |
| Crate | [package] |
| Modules | mod / pub |
| Dependencies | [dependencies] |
Cargo handles crates, Rust handles modules.