Figuring out how Rust's SemVer works
Tags: rust
Published: 2022-11-25 03:48:11

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](https://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](https://doc.rust-lang.org/cargo/reference/semver.html)