When Cargo refuses to build
You add a new crate to your project. You run cargo build. The terminal spits back a wall of red text ending in error: failed to select a version for the requirement. It feels like Cargo is broken. Cargo isn't broken. It's protecting you from a dependency conflict that would break your build later.
The error means Cargo tried to build a dependency graph and found a contradiction. Two crates demand incompatible versions of a third crate, or a crate you asked for simply doesn't exist in the registry. Cargo refuses to guess. It halts and tells you exactly where the graph collapsed.
The constraint solver behind the error
Cargo's dependency resolver is a constraint satisfaction engine. It treats your project as a graph. Your crate is the root. Every dependency is a node. Edges connect crates to the versions they require. Cargo's job is to pick exactly one version for every crate in the graph such that every edge's constraint is satisfied.
If crate A requires log 0.4 and crate B requires log 0.3, Cargo has to find a single version of log that is both 0.4.x and 0.3.x. That's impossible. The solver gives up and reports the failure.
Think of it like planning a dinner party. You invite Alice, who only eats vegan food. You invite Bob, who only eats steak. You try to order from a restaurant. If the restaurant has a menu that satisfies both, you're good. If the menu has only vegan dishes or only meat dishes, you can't seat both guests. Cargo is the waiter telling you the menu doesn't work for your guest list.
Cargo prefers to fail fast. Picking a version that satisfies one crate but breaks another would lead to cryptic compiler errors later. The resolver catches the problem at the source.
Minimal example: A direct conflict
Create a project with two dependencies that pull in incompatible versions of a shared library.
# Cargo.toml
[package]
name = "conflict-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
# WHY: foo requires common ^1.0
foo = "1.0"
# WHY: bar requires common ^2.0
bar = "1.0"
Assume foo depends on common = "1.0" and bar depends on common = "2.0". When you run cargo build, Cargo queries the registry. It sees common 1.0.5 and common 2.0.1. It tries to unify the requirements. It needs a version of common that satisfies ^1.0 and ^2.0. No such version exists.
The output looks like this:
error: failed to select a version for the requirement common = ^1.0
candidate versions found which didn't match: 2.0.1
required by package bar v1.0.0
The error message is a map. The first line names the requirement that failed. The second line lists versions that exist but don't match. The third line points to the crate that demanded the failing requirement. Follow that chain to find the root cause.
How Cargo resolves dependencies
Cargo reads Cargo.toml to get the direct dependencies. It checks Cargo.lock if it exists. The lock file pins exact versions for every crate in the tree. If the lock file is present and up to date, Cargo skips the registry and uses the pinned versions. This makes builds reproducible.
If the lock file is missing or you run cargo update, Cargo queries the registry. It builds a graph of all dependencies, including transitive ones. It runs the solver. The solver attempts to assign versions to nodes. If it hits a conflict, it backtracks and tries alternatives. If no solution exists, it reports the error.
The error often appears when the lock file is stale. You update a dependency in Cargo.toml, but the lock file still holds an old version of a transitive dependency that conflicts with the new requirement. Running cargo update refreshes the lock file and resolves the conflict.
Convention aside: The community treats Cargo.lock as the single source of truth for reproducible builds. If you build a binary, commit Cargo.lock. If you build a library, don't commit it. The error frequently surfaces when a lock file is out of sync with Cargo.toml.
Realistic scenario: Transitive dependency clash
A common real-world case involves upgrading a major dependency. You have a web server using actix-web 3. You want to upgrade to actix-web 4. You change the version in Cargo.toml. Suddenly, actix-web 4 requires tokio 1.0, but an old middleware crate you're using still requires tokio 0.2.
# Cargo.toml
[dependencies]
# WHY: actix-web 4 pulls in tokio 1.0
actix-web = "4"
# WHY: old-middleware is stuck on tokio 0.2
old-middleware = "0.1"
Cargo fails with:
error: failed to select a version for the requirement tokio = ^0.2
candidate versions found which didn't match: 1.28.0
required by package old-middleware v0.1.0
The conflict is between actix-web and old-middleware. You have three options. Update old-middleware to a version that supports tokio 1.0. Replace old-middleware with a modern alternative. Or use the [patch] section to force a version, though that's a last resort.
Debugging the graph
When the error message doesn't make sense, use cargo tree. This command prints the dependency graph. It shows every crate and its version.
Run cargo tree --duplicates to see if multiple versions of the same crate exist in the graph. Duplicate versions often indicate a conflict, even if resolution succeeded. They can cause runtime issues if crates expect to share types.
# WHY: Shows the full tree with duplicate versions highlighted
cargo tree --duplicates
Use cargo tree -i crate-name to find reverse dependencies. This shows which crates pull in a specific dependency. It helps you trace the path to a conflict.
# WHY: Finds all crates that depend on tokio
cargo tree -i tokio
Convention aside: cargo tree -i is a pro move. It saves you from guessing which crate is pulling in a problematic dependency. Use it whenever you see a crate in the error message that you didn't add yourself.
Pitfalls and edge cases
A stale Cargo.lock is the most common cause. You update a dependency, but the lock file holds an old transitive dependency. Run cargo update to fix it.
Typos in Cargo.toml can trigger the error. If you type dep = "1.0" but the crate is named depp, Cargo can't find it. The error message usually says "no matching package found". Double-check the crate name.
Private registries or network issues can manifest as this error. If Cargo can't reach the registry, it sees no versions. Check your network connection. Verify your .cargo/config.toml if you use a private registry.
MSRV issues rarely cause this error directly. If a crate requires a newer Rust version, Cargo usually reports a different error. However, if a crate's metadata is malformed or missing, you might see a resolution failure. Ensure your Rust toolchain is up to date.
cargo update without arguments updates every dependency to the latest compatible version. This can introduce breaking changes in transitive dependencies. Use cargo update -p crate-name to bump a specific crate. This surgical approach is safer.
Convention aside: cargo update -p is the community standard for targeted updates. It avoids dragging in unrelated changes. Use it when you know which crate is causing trouble.
Decision matrix: Tools for resolution
Use cargo update when the error mentions a version that exists in the registry but isn't in your lock file.
Use cargo update -p crate-name when you need to bump a specific transitive dependency without touching the rest of the tree.
Use cargo tree --duplicates when you suspect version fragmentation or want to see why a conflict exists.
Use cargo tree -i crate-name when you need to find which crates are pulling in a problematic dependency.
Use [patch] in Cargo.toml when you maintain a fork of a dependency and need to force the graph to use your local version.
Use version relaxation (e.g., changing = 1.2.3 to ^1.2) when you pinned a version too tightly and a compatible update is available.
Use cargo add when adding new dependencies to let Cargo pick a sensible default version.
Patch only when you have to. The graph knows best.