Cargo.toml describes a Cargo package: its name, Rust edition, dependencies
and build settings. The Rust exercises here use a direct compiler invocation,
so try this article's examples in a local Cargo project.
Start with package metadata
This manifest works with a source file at src/lib.rs or src/main.rs:
[package]
name = "image-proc"
version = "0.3.1"
edition = "2024"
rust-version = "1.85"
description = "Image preprocessing utilities"
license = "MIT OR Apache-2.0"
repository = "https://github.com/you/image-proc"
Replace the example's name, description, license and repository with your own. The license expression means users may choose either license; include the corresponding license files when distributing the package.
Set edition explicitly. If omitted, Cargo assumes edition 2015 for backward
compatibility. cargo new writes an edition field for you. An edition selects
language rules; it does not pin the compiler version.
rust-version declares the minimum supported Rust version, often called the
MSRV. Cargo normally rejects an older compiler before building the package.
The edition-2024 resolver prefers compatible dependency versions when possible,
but that preference cannot guarantee that every dependency has a compatible
release. Test on your declared minimum version in CI to check the promise.
Publishing also needs suitable metadata. crates.io requires a description and
license information, and a package version is required for publishing. A
repository link and README help users understand and evaluate the crate.
For a package you do not intend to publish, set publish = false under
[package].
Control what goes into the package
Large test fixtures may belong in your repository without belonging in the
downloadable crate. Add packaging patterns under [package], for example:
exclude = ["fixtures/**", "bench-data/**"]
Alternatively, use an explicit list of files to include:
include = ["src/**", "Cargo.toml", "README.md", "LICENSE-*"]
include takes precedence if both fields are present; it does not combine
with exclude to subtract files. Prefer one approach so the intent is clear.
An allowlist must also cover any build script, generated-code inputs or other
assets the package needs.
Without these fields, Cargo uses its default selection rules, including Git ignore rules when applicable and special treatment for files such as the manifest, lockfile, nested packages and build output. Do not guess the archive's contents from the repository listing. Inspect them with:
cargo package --list
This lists package contents without publishing. Review the list before a release, especially after adding fixtures or changing packaging patterns.
Put dependencies in the right table
These are example version requirements, not a list of the newest releases:
[dependencies]
serde = { version = "1", features = ["derive"] }
rand = "0.8"
[dev-dependencies]
criterion = "0.5"
[build-dependencies]
cc = "1"
[target.'cfg(unix)'.dependencies]
nix = "0.29"
| Table | Where the dependency is available |
|---|---|
dependencies |
Ordinary package code, including library and binary targets |
dev-dependencies |
Tests, examples and benchmarks, including unit tests inside src/lib.rs |
build-dependencies |
The build script, normally build.rs, compiled for the host machine |
target.'cfg(...)'.dependencies |
Ordinary code when the target condition matches |
A dev dependency cannot be used by ordinary library code just because the
dependency exists in your manifest. Put test-only uses in a #[cfg(test)]
module. Consumers of your library do not inherit its dev dependencies as
requirements for building the library.
Target predicates such as cfg(unix) and cfg(target_os = "windows")
describe the compilation target. These tables do not support arbitrary Rust
conditions: for example, use [features] and optional dependencies instead
of cfg(feature = "...") to select dependencies by feature. cfg(test)
is not supported for selecting dependencies either.
To use a dependency under a different name in your code, set package:
[dependencies]
rand_old = { package = "rand", version = "0.7" }
rand = "0.8"
Here, rand_old and rand refer to two incompatible release lines of the
same package. They both have major version zero; Cargo treats these different
minor versions as incompatible. Types from the two versions are distinct,
even when they share a name. Keep both only when your project needs them.
Configure lints
Cargo supports manifest lint settings from Rust 1.74 onward. Source attributes
such as #![deny(unsafe_code)] remain valid too. Append these tables to the
package manifest:
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
all = { level = "deny", priority = -1 }
needless_range_loop = "allow"
The all group applies first because its priority is lower. The individual
lint has the default priority, zero, so it can override the group's deny.
Give groups and their overrides different priorities instead of relying on
their order in the TOML file. forbid is stronger than deny: it cannot be
relaxed by a source-level allow.
These settings apply to this package, not as a blanket policy for all its
dependencies. cargo clippy checks Clippy lints; a normal cargo build does
not run those checks.
Keep a lockfile for repeatable development
Commit Cargo.lock for applications and libraries. It records the resolved
dependency versions used to develop and test the project. A library consumer
resolves dependencies using their own project; your library's lockfile does
not pin their build.
In CI, cargo test --locked requires the existing lockfile to remain unchanged.
This helps prevent unnoticed dependency resolution changes. It does not pin
the Rust toolchain, system libraries or build environment, and it may still
need to download dependencies. Check updates deliberately with your test suite.
Optional checks for manifest quality
Clippy's cargo lint group is opt-in:
cargo clippy -- -W clippy::cargo
It includes checks for missing package metadata, wildcard dependency requirements and multiple versions of a dependency. These are prompts for review: a lint cannot tell whether two dependency versions are intentional.
Clippy also has feature-name checks, such as negative_feature_names and
redundant_feature_names. They flag naming patterns, not proof that a
feature removes functionality or breaks a build. See the
Cargo feature-resolution exercise
for why compatible, additive features are easier to combine.
Use the Cargo manifest reference when you need the full rules for a field.