Learning Rust - Packages, Crates, and Dependency Management
Episode 7 of 19

Learning Rust - Packages, Crates, and Dependency Management

This episode discusses package management in Rust: structuring Cargo.toml with features and dependency versions, building multi-crate workspaces for large applications, as well as keeping dependencies secure with cargo audit and cargo tree.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

So far we have worked with a single small crate. Real applications are much larger: dozens of dependencies, several binaries and libraries, and the need to verify that all of it is secure. Episode 7 discusses managing all of that with cargo.

You will structure Cargo.toml correctly, understand features and semver, build multi-crate workspaces, and then secure the dependency chain with cargo audit. By the end of the episode, your Rust project will be ready to grow into an organized codebase.

Structuring Cargo.toml

The Manifest and Its Parts

Cargo.toml is the center of package configuration. The [package] section declares metadata, while [dependencies] lists external crates with semver versions.

Cargo.toml
[package]
name = "aplikasi"
version = "0.1.0"
edition = "2024"
 
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.40", features = ["full"] }

Version 1.40 follows semver: cargo allows all compatible versions upward within the same major. Cargo.lock then pins the exact version for reproducible builds. The cargo add command manages this section automatically:

Adding a dependency
cargo add serde --features derive
cargo add tokio --features full

cargo add serde --features derive adds serde with the derive feature enabled. Cargo.lock is updated, and the whole team gets the same version.

Features: Collections of Optional Dependencies

Features are a mechanism for conditional compilation: certain dependencies or code are only enabled when the feature is enabled. This allows a single crate to serve many needs without burdening its users.

Features in Cargo.toml
[features]
default = ["api"]
api = ["dep:axum"]
cache = ["dep:redis"]
 
[dependencies]
axum = { version = "0.8", optional = true }
redis = { version = "0.27", optional = true }

The api and cache features bring their respective dependencies to life. Users only pay for the features they use. cargo tree --features api shows the dependencies active for a given feature combination.

Dependency Versions and Resolution

Understanding Semver in the Cargo Ecosystem

Cargo uses semantic versioning: major.minor.patch. Patch and minor updates are compatible, major changes can break the API. Cargo also supports ranges like 1.2, >=1.3, <2.0, or an exact version =1.2.3.

Viewing the dependency tree
cargo tree
cargo tree -i axum

cargo tree displays all dependencies and their versions. cargo tree -i axum answers the question "who uses axum and why". When dependencies are pulled in layer by layer, this tree becomes the map for resolving conflicts.

Updating Dependencies

Update and outdated
cargo update
cargo outdated

cargo update updates versions within the range allowed by Cargo.lock. cargo outdated (from cargo install cargo-outdated) shows dependencies that are out of date. Both are part of a regular maintenance routine.

Multi-Crate Workspaces

Why Workspaces

For large applications, split into several crates within one workspace: a shared compilation cache, a single Cargo.lock, and unified versioning. A common structure: one workspace with a crates/ directory containing binary and library crates.

Cargo.toml workspace
[workspace]
members = ["crates/domain", "crates/api", "crates/cli"]
resolver = "2"
 
[workspace.package]
version = "0.1.0"
edition = "2024"

Each crate in members has its own Cargo.toml, and they can depend on one another via path:

Dependency between crates
cargo add --path crates/domain --package crates/api

cargo add --path crates/domain --package crates/api adds a local dependency between crates. cargo build from the root builds the entire workspace. cargo run -p crates/cli runs a specific binary.

Dependency Security with Cargo Audit

Scanning for Vulnerabilities

The crate ecosystem is open, but it can also contain vulnerabilities. cargo audit scans Cargo.lock against the RustSec vulnerability database:

Install and run audit
cargo install cargo-audit
cargo audit

cargo audit displays vulnerabilities with severity, package, safe version, and advisory reference. This should be part of CI, not just a local routine.

Dependency Security Practices

A few habits that keep the dependency chain safe:

  • Always run cargo audit in CI, and fail the build when there is a critical advisory.
  • Use cargo deny to enforce license policy and duplicate dependency rules.
  • Pin high-risk dependencies and review before major updates.
  • Avoid --git dependencies unless truly necessary, because there is no stable checksum.

Warning

A dependency that pulls in many other crates can drag in duplicate versions. cargo tree -d shows duplicated dependencies — clean them up with cargo update so binaries are smaller and resolution is simpler.

Closing

Key takeaways:

  • Cargo.toml declares the package, dependencies, and features; Cargo.lock pins versions.
  • Semver determines the version ranges cargo allows.
  • Features enable dependencies and code conditionally.
  • Multi-crate workspaces split large applications with a single lockfile.
  • cargo audit scans for vulnerabilities; cargo deny manages policy.
  • Routinely update dependencies and check for duplicates with cargo tree -d.

In the next episode 8 we will discuss data serialization, persistence, and I/O — serialization with serde for JSON and TOML formats, file I/O and async I/O with tokio, as well as database integration with sqlx, diesel, or sea-orm. You will start storing and loading data.