I was wondering how Rust's SemVer works, so I ran some experiments. For those who don't know, SemVer is what Cargo uses to decide what version of a depenecy it should use, because it might not be excactly what you specified.
doc.rust-lang.org/cargo/reference/resolver.html does a good job explaning how it works, but I wanted to run some experiments to help wrap my head around it. Here are the results:
cargo b
is the same as cargo build
, which I'm just running so Cargo will download the depencies.
Rust Analyzer is never running, it might start downloading stuff before I'm ready.
No Cargo.lock
Latest serde_json version is v1.0.89
Setting Cargo.toml to depend on serde_json = "1.0.0"
cargo b
serde_json v1.0.89 is downloaded
Deleted Cargo.lock
Latest serde_json version is v1.0.89
Setting Cargo.toml to depend on serde_json = "1.0.90"
cargo b
error: failed to select a version for the requirement serde_json = "^1.0.90"
No Cargo.lock file generated
No Cargo.lock file
Latest serde_json version is v1.0.89
Setting Cargo.toml to depend on serde_json = "0./*"
cargo b
serde_json v0.9.10 is downloaded
Deleted Cargo.lock
Latest serde_json version is v1.0.89
Setting Cargo.toml to depend on serde_json = "0.0./*"
cargo b
error: failed to select a version for the requirement serde_json = "0.0./*"
No Cargo.lock file generated
No Cargo.lock file
Latest serde_json version is v1.0.89
Setting Cargo.toml to depend on serde_json = "0./*./*"
cargo b
serde_json v0.9.10 is downloaded
Deleted Cargo.lock
Removed dependency in Cargo.toml
Latest rand version is v0.8.5
Setting Cargo.toml to depend on rand = "0.8.0"
cargo b
rand v0.8.5 is downloaded
Deleted Cargo.lock
Removed dependency in Cargo.toml
Latest bitflags version is v1.3.2
Setting Cargo.toml to depend on bitflags = "1.0.0"
cargo b
bitflags v1.3.2 is downloaded
Conclusion
Always specify versions with all 3 numbers.
The latest version that is on the same MAJOR version will be downloaded.
In versions >= 1.0.0, with x.y.z, x is the major version, y and z are minor versions.
In versions < 1.0.0, with 0.y.z, y is the major version, z is the minor version.
According to the docs, in 0.0.z, z is the major version.
This is fine because minor version changes shouldn't break anything if the crate is SemVer compliant, and here's a situation where this flexibility works out:
A depends on B v1.0.0
A depends on C v1.0.0
C depends on B v1.1.0
The latest version of B is v1.1.1
The latest version of C is v1.0.0
When A is built, it will use B v1.1.1, and C 1.0.0, which will use B v1.1.1, there won't be a situation where there are two versions of the same library.
This also works the other way around, with A having a newer version of B than C, C will upgrade its B.
Multiple versions of the same library will exist if, for instance:
A depends on B v1.0.0
A depends on C v1.0.0
C depends on B v2.0.0
In this case, A will have to compile two separate versions of B, but that's ok because it means that even when B is on version 999.999.999 and the library structure is completely different, the not updated C will still compile, using the latest version of C v2././, getting any updates that happened that didn't qualify for a major update, which can include security patches and optimizations.
Major vs Minor update
A major update is something that will cause code using a previous version to break, such as changing the signature of a public function.
A minor update is something that will NOT cause code using a previous version to break, such as generalizing a function to use generics, and the new generics support the original type.
You can read more about what is a minor or major change at doc.rust-lang.org/cargo/reference/semver.html
Top comments (0)