23.4 The Manifest: Cargo.toml

The Cargo.toml file is the heart of a Rust package (crate). It uses the TOML (Tom’s Obvious, Minimal Language) format to define metadata and dependencies.

23.4.1 Common Sections

A typical Cargo.toml includes several sections:

[package]
name = "my_crate"
version = "0.1.0"
edition = "2021" # Specifies the Rust edition (e.g., 2015, 2018, 2021)
authors = ["Your Name <you@example.com>"]
description = "A short description of what my_crate does."
license = "MIT OR Apache-2.0" # SPDX license expression
repository = "[https://github.com/your_username/my_crate](https://github.com/your_username/my_crate)" # Optional: URL to source repo
readme = "README.md" # Optional: Path to README file
keywords = ["cli", "utility"] # Optional: Keywords for Crates.io search

[dependencies]
# Lists crates needed to compile and run the main code
serde = { version = "1.0", features = ["derive"] } # Example with version and features
rand = "0.8"
log = "0.4"

[dev-dependencies]
# Lists crates needed only for tests, examples, and benchmarks
assert_cmd = "2.0"
criterion = "0.4"

[build-dependencies]
# Lists crates needed by build scripts (build.rs)
# Example: cc = "1.0"

[features]
# Defines optional features for conditional compilation
default = ["std_feature"] # Default features enabled if none specified
std_feature = []
serde_support = ["dep:serde"] # Feature enabling an optional dependency

[profile.release]
# Customizes the 'release' build profile (e.g., for optimizations)
opt-level = 3    # Optimization level (0-3, 's', 'z')
lto = true       # Enable Link-Time Optimization
codegen-units = 1 # Fewer codegen units for potentially better optimization

# See also: [profile.dev], [profile.test], [profile.bench]
  • [package]: Core metadata about the crate. Fields like name, version, edition, description, and license are essential, especially if publishing to Crates.io.
  • [dependencies]: Lists the crates your package depends on to run. Cargo downloads these from Crates.io by default.
  • [dev-dependencies]: Crates needed only for development tasks like running tests, benchmarks, or examples. They are not included when someone uses your crate as a dependency.
  • [build-dependencies]: Crates required by a build.rs script (a script Cargo runs before compiling your crate, often used for code generation or compiling C code).
  • [features]: Allows defining optional features that enable conditional compilation, often used to toggle functionality or optional dependencies.
  • [profile.*]: Sections for customizing build profiles (dev, release, test, bench). (See Section 23.6).

23.4.2 Specifying Dependencies

Dependencies are listed under the [dependencies] (or [dev-dependencies], [build-dependencies]) section. The simplest form specifies the crate name and a version requirement:

[dependencies]
regex = "1.5"

Cargo uses Semantic Versioning (SemVer). The version string "1.5" is shorthand for "^1.5.0", meaning Cargo will accept any version v where 1.5.0 <= v < 2.0.0. This allows compatible minor and patch updates automatically. Other common specifiers include:

  • "~1.5.2": Allows only patch updates (>= 1.5.2, < 1.6.0).
  • "=1.5.2": Requires exactly version 1.5.2.
  • ">=1.5.0, <1.6.0": Specifies an explicit range.
  • "*": Accepts any version (use with caution).

You can also specify dependencies from other sources:

[dependencies]
# From a Git repository
some_lib = { git = "[https://github.com/user/some_lib.git](https://github.com/user/some_lib.git)", branch = "main" }

# From a local path (useful during development or in workspaces)
local_util = { path = "../local_util" }

# With optional features enabled
serde = { version = "1.0", features = ["derive"] }

# Marked as optional (only included if a feature enables it)
# In [dependencies]:
#   mio = { version = "0.8", optional = true }
# In [features]:
#   network = ["dep:mio"]

23.4.3 The Cargo.lock File

When you build your project for the first time, or after modifying dependencies in Cargo.toml, Cargo resolves all dependencies (including transitive ones) and records the exact versions used in the Cargo.lock file.

  • Purpose: Ensures reproducible builds. Anyone building the project with the same Cargo.lock file will use the exact same dependency versions, preventing unexpected changes due to automatic updates.
  • Management: Cargo.lock is automatically generated and updated by Cargo commands like build, check, add, remove, or update. You should not edit it manually.
  • Version Control:
    • For binary applications: Always commit Cargo.lock to version control. This guarantees that every developer, CI system, and deployment uses the same dependency set.
    • For libraries: Committing Cargo.lock is optional and debated.
      • Pro-Commit: Ensures the library’s own tests run with a consistent set of dependencies in CI.
      • Anti-Commit: Libraries are typically used as dependencies themselves. The downstream application’s Cargo.lock will ultimately determine the versions used. Committing the library’s Cargo.lock doesn’t affect consumers and might cause merge conflicts. Many library authors choose not to commit Cargo.lock.

23.4.4 Updating Dependencies

  • cargo update: Reads Cargo.toml and updates dependencies listed in Cargo.lock to the latest compatible versions allowed by the version specifications in Cargo.toml. It does not change Cargo.toml itself.
    • cargo update -p <crate_name>: Updates only a specific dependency and its dependents.
  • Upgrading Dependencies (Major Versions): To use a new major version (e.g., moving from serde “1.0” to “2.0”), you must manually edit the version requirement in Cargo.toml. Tools like cargo-edit (cargo upgrade) can assist with this.
  • Checking for Outdated Dependencies: Use cargo outdated (from the cargo-outdated tool) to see which dependencies have newer versions available than what’s currently in Cargo.lock.