DEV Community

Zachary Perkins
Zachary Perkins

Posted on • Edited on

Figuring out how Rust's SemVer works

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)