---
title: Cargo.toml Deep Dive
url: https://doc.liz6.com/en/rust/09-cargo-and-build/01-cargo-toml-deep-dive
locale: en
area: rust
tags:
- rust
- cargo-and-build
date: 2026-06-30
modified: 2026-07-16
description: Cargo.toml is more than just "listing dependencies"—features handle conditional compilation and optional functionality, profiles control optimization levels for debug/release builds, and build scripts execute arbitrary code before compilation (generating bindings, compiling C libraries). The correct way to write each configuration corresponds to a common pitfall.
---

# Cargo.toml Deep Dive

> Cargo.toml is more than just "listing dependencies"—features handle conditional compilation and optional functionality, profiles control optimization levels for debug/release builds, and build scripts execute arbitrary code before compilation (generating bindings, compiling C libraries). The correct way to write each configuration corresponds to a common pitfall.

## [dependencies]: Version Specification

```toml
[dependencies]
serde = "1"                          # ^1.0.0: >=1.0.0, <2.0.0 (semver compatible)
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"                       # ^1.0.0

[dev-dependencies]
criterion = "0.5"                    # Only available in tests/benches

[build-dependencies]
cc = "1"                             # Only available in build.rs

[target.'cfg(target_os = "linux")'.dependencies]
x11 = "2"                            # Linux only
```

Version Rules: Cargo uses semver (Semantic Versioning). `"1.2.3"` = exact match, `^1.2.3` (default) = >=1.2.3 and <2.0.0. `Cargo.lock` pins exact versions—library crates should not commit the lockfile, while binary crates should.

## [features]: Conditional Compilation

```toml
[features]
default = ["std", "tls"]
std = []
tls = ["tokio", "rustls"]            # Activates tokio + rustls
json = ["serde", "serde_json"]
nightly = []
```

Usage: `cargo build --no-default-features --features "json,nightly"`. Features are **additive**—you can only add code, not remove it. Two independent features cannot conflict with each other.

## [profile]: Compilation Optimization

```toml
[profile.release]
opt-level = 3                        # Maximum optimization (0-3, s=size, z=size aggressively)
lto = "fat"                          # Link-Time Optimization
codegen-units = 1                    # Single codegen unit → more inlining, slower compilation
panic = "abort"                      # No unwinding (smaller binary, suitable for embedded)

[profile.dev]
opt-level = 0                        # Fast compilation, no optimization
debug = true
```

Release Configuration Trade-offs: `opt-level=3` + `lto=fat` + `codegen-units=1` = smallest and fastest binary, but longest compilation time (possibly 10-30 minutes). Use defaults for daily development (opt-level=3, thin lto, codegen-units=16), and highest settings in CI.

## [workspace.dependencies]: Shared Versions

```toml
# root Cargo.toml:
[workspace.dependencies]
serde = "1"
tokio = "1"

[dependencies]
serde = { workspace = true }         # References the workspace version
```

This feature (1.64+) solves the classic problem of "inconsistent dependency versions across multiple crates"—upgrading serde requires changing only one place.

## build.rs: Compile-Time Scripts

```rust
// build.rs — executed **before** Cargo compiles the main code:
fn main() {
    println!("cargo:rustc-link-lib=z");      // Link libz
    println!("cargo:rerun-if-changed=wrapper.h");  // Re-run only if the header changes
    cc::Build::new().file("src/c/wrapper.c").compile("wrapper");
}
```

build.rs is commonly used for: compiling C/C++ dependencies, detecting system libraries, code generation (protobuf, OpenAPI), and setting environment variables. Output is in the `OUT_DIR` environment variable—managed by Cargo, no manual cleanup needed.

## References

- **Cargo Book**: doc.rust-lang.org/cargo
- **The Cargo Book**: profiles, features, build scripts

*Keywords: Cargo.toml, features, dependencies, profile, LTO, build.rs, workspace*
