Skip to main content

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"
ConceptPurpose
ModuleOrganize code inside a crate
CrateCompilation unit
WorkspaceOrganize 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)

VersionMeaning
"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.lock for 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

LayerControlled By
WorkspaceRoot Cargo.toml
Crate[package]
Modulesmod / pub
Dependencies[dependencies]

Cargo handles crates, Rust handles modules.