Dependency Version Requirements
Dependency version requirements specify which versions of an external crate are compatible with your package in Cargo.toml. By default, Cargo interprets bare version strings using Caret (^) Semantic Versioning rules, allowing automatic non-breaking updates while preventing breaking changes.
Caret Requirement (Default in Cargo):
"1.2.3" === "^1.2.3" ---> >=1.2.3, <2.0.0 (1.x.y is compatible)
"0.2.3" === "^0.2.3" ---> >=0.2.3, <0.3.0 (0.2.x is compatible; 0.3.0 may break!)
"0.0.3" === "^0.0.3" ---> >=0.0.3, <0.0.4 (Exact only for 0.0.z)
Version Requirement Operators in Cargo.toml
Notice how Cargo treats 0.y.z pre-1.0 versions specially: before 1.0.0, the leftmost non-zero digit acts as the major breaking version number.
| Operator Syntax | Example | Equivalent Range | When to Use |
|---|---|---|---|
Bare / Caret (^) |
"1.4" or "^1.4.2" |
>=1.4.2, <2.0.0 |
Default & Recommended for almost all dependencies. |
| Pre-1.0 Caret | "0.4.2" |
>=0.4.2, <0.5.0 |
Standard for 0.y.z crates where 0.5.0 can introduce breaking API changes. |
Tilde (~) |
"~1.4.2" |
>=1.4.2, <1.5.0 |
Restricts updates to patch releases (1.4.x) only. |
Wildcard (*) |
"1.*" |
>=1.0.0, <2.0.0 |
Equivalent to "^1.0.0" (crates.io rejects bare "*" wildcards). |
| Inequality / Compound | ">= 1.2, < 1.5" |
>=1.2.0, <1.5.0 |
Excluding known incompatible minor versions. |
Exact (=) |
"=1.4.2" |
Exactly 1.4.2 |
Tightly coupled companion crates (such as my_crate and my_crate_macros); avoid in general libraries to prevent version conflicts. |
Why Exact Pinning ("=1.2.3") Should Be Avoided in Libraries
Cargo allows at most one version of a crate per major SemVer track (e.g., one 1.x version of serde) in the same dependency graph unless package renaming is used. If Library A requires serde = "=1.0.200" and Library B requires serde = "^1.0.210", Cargo cannot satisfy both constraints simultaneously and fails dependency resolution!
Instead:
- Specify the minimum version that provides the APIs you actually call in
Cargo.toml(e.g.,serde = "1.0.190"or"1.0"). - Rely on
Cargo.lock(not"=..."inCargo.toml) to pin exact versions for reproducible builds.
#[derive(Debug, PartialEq, Eq)]
struct SemVer {
major: u32,
minor: u32,
patch: u32,
}
impl SemVer {
fn satisfies_caret(&self, req: &SemVer) -> bool {
if self.major != req.major {
return false;
}
if req.major > 0 {
(self.minor, self.patch) >= (req.minor, req.patch)
} else if req.minor > 0 {
self.minor == req.minor && self.patch >= req.patch
} else {
self.minor == 0 && self.patch == req.patch
}
}
}
fn main() {
let req_1_2 = SemVer { major: 1, minor: 2, patch: 3 };
let v_1_9 = SemVer { major: 1, minor: 9, patch: 0 };
let v_2_0 = SemVer { major: 2, minor: 0, patch: 0 };
println!("1.9.0 satisfies ^1.2.3: {}", v_1_9.satisfies_caret(&req_1_2));
println!("2.0.0 satisfies ^1.2.3: {}", v_2_0.satisfies_caret(&req_1_2));
let req_0_2 = SemVer { major: 0, minor: 2, patch: 3 };
let v_0_3 = SemVer { major: 0, minor: 3, patch: 0 };
println!("0.3.0 satisfies ^0.2.3: {}", v_0_3.satisfies_caret(&req_0_2));
}
1.9.0 satisfies ^1.2.3: true
2.0.0 satisfies ^1.2.3: false
0.3.0 satisfies ^0.2.3: false