Different real world ecosystems come with various SemVer philosophies. At least MSRV handling within the Rust crates ecosystem is solved — but maybe should never have been problematic in the first place?

[Note from moderators]: The first 9 posts in this topic [including this OP] were moved here from another topic - Concerns on the Long Term Viability of Rust for Real World Applications - #26 by philomathic_life


This is a bit of a tangent, but: ever since the MSRV resolver was added, there is no longer any reason to consider MSRV a semver breaking thing. Rust doesn't break backwards compatibility, so it is entirely equivalent to updating one of my dependencies by a minor version. That does not turn into a breaking change for me.

This was of course true before as well, but with the MSRV aware resolver you no longer cause issues for those who for some reason stay on older compiler versions. They can now get matching old versions of crates.

(Side note: it makes no sense to me why some people want to use new crates and an old compiler, use old of both or new of both. You can't have your cake and eat it too...)

It was indeed, and I should have made that a footnote to not derail this convo too much. I know now it's not a problem specifically if you use rustc 1.84.0 or newer and have set resolver.incompatible-rust-versions to fallback (e.g., by using the 2024 edition), but many crates still define an MSRV long before rustc 1.84.0; thus this will be a problem for many. Point is each community has their own notion of what is covered by SemVer; and even ones that normally "strictly" adhere to SemVer often have some "unspoken" exceptions. The strictest notion of SemVer is simply a build today works tomorrow unless you have changed at least one of your dependencies to a version that has a larger major version in which case such a guarantee no longer exists. The more exceptions you employ, the more meaningless (or at least hard to understand) SemVer becomes.

This "blessing of MSRV as an exception to SemVer" isn't avoided when one uses "old of both". That's precisely the issue. This is due to the fact that because MSRV is typically not part of SemVer and the community has accepted this thus either accidentally or intentionally defines their dependencies via caret requirements, it's trivial for code to suddenly not compile when neither the code nor compiler have been touched. Even more damning, if I decide to define a dependency using an exact requirement, I'm still not protected. The only way to "solve" this when one doesn't have rustc 1.84.0 or newer is by using the Cargo.lock file which defeats the point. Proof:

my_crate:

[package]
name = "my_crate"
version = "1.0.0"

[dependencies]
# I don't trust you `other_crate` to adhere to SemVer, so
# I require _exactly_ this version!
other_crate = "=1.0.0"

other_crate:

[package]
name = "other_crate"
version = "1.0.0"

[dependencies]
# I trust `yet_another_crate` to adhere to SemVer and either
# know this includes the exception to MSRV or don't but am so
# used to using caret requirements.
yet_another_crate = "1.0.0"

yet_another_crate:

[package]
name = "yet_another_crate"
version = "1.0.0"
rust-version = "x.y.z"
[zack@laptop my_crate]$ cargo -V
cargo x.y.z
[zack@laptop my_crate]$ cargo check
Everything is good!

Time passes. yet_another_crate updates their MSRV but only increments the minor version since that's the Rust way:

[package]
name = "yet_another_crate"
version = "1.1.0"
# `b` > `y`.
rust-version = "x.b.c"

Haven't touched anything including my compiler:

[zack@laptop my_crate]$ cargo -V
cargo x.y.z
[zack@laptop my_crate]$ cargo check
Compilation failure!

This is because even though my_crate will grab 1.0.0 of other_crate, it also has to grab its dependencies; however other_crate informs cargo to grab the maximally compatible version of yet_another_crate which x.b.c is. Unfortunately x.b.c is newer than the version of cargo on my machine, and bam! Failure.

Version control your lock file. And your toolchain file. Problem solved.

And the MSRV aware resolver fixes it for updating now. You can even (if you set the rust version in your Cargo.toml) use a newer cargo to do the updates for a pre-MSRV aware version.

Of course. That's a workaround that always applies. If I adopt a SemVer policy that states "any function that has its return type changed is exempt from SemVer" and someone complains, I can always reply "version control your lock file"; but many would find that not acceptable since I violated SemVer. Once you expect users to lock down their specific dependencies, then you've departed entirely from SemVer.

Again, I don't believe MSRV is a real or valid problem.

If I update from depending on tokio 1.40 to 1.41 it is not a breaking change. If I update from rustc 1.86 to 1.87 it is not a breaking change. The compiler/std is not in a privileged position as a dependenxy.

I don't understand why you would complain about being forced to move to a newer compiler, but not a newer tokio.

The issue only arises if you insist on mixing two diffrent package manger ecosystems: e.g. Installing rustc from Debian, but using cargo for crates. The correct approach is to use the same ecosystem for the whole chain (rustup+cargo xor distro packages).

But even if you do this, it is no longer an issue as we have already had an MSRV aware resolver for over half a year, which is quite a long time as far as I'm concerned. I generally have a MSRV policy of N-1 for my libraries and N for applications that I write. N-1 gives some time to upgrade and test things out and report regressions, which should be sufficient.

(If you need more that likely indicates a lack of reliable automated testing or some other process issue, that you need to solve anyway. You should be running your test suite on beta or nightly rust every so often. Doesn't have to be every PR, but maybe once a week would be good.)

Fortunately the Rust team disagreed; as if they didn't, we never would have gotten a new resolver. This was done because targets that don't only support stable are targets Rust wants to target even if they are targets you don't care about and there was far too much inertia behind the idea that MSRV was exempt from SemVer that they knew the tool needed to handle it since it was a lost cause to expect a large contingent of Rustaceans to suddenly treat MSRV as part of the SemVer policy. Maybe in the future a tool will be able to handle other SemVer violations automatically too like detecting when a return type is suddenly different just because a large number of people deemed it exempt from SemVer.

It's not. You chose it to be by defining an MSRV. No one forced you to define an MSRV; and frankly seeing how you seem to only care about stable, I find it very bizarre why you bother defining an MSRV at all.

I'm very confused. You're setting MSRV to nightly?

Or are you talking about the stable version, rather than a stable version - in which case I'm not sure how you're making that assumption. Clearly if you're talking about supporting a minimum version, you're talking about a version that might not be the current version.

I mean I guess you could have a "current only" MSRV policy, but that's implicitly what you have by default so I don't think anyone really explicitly states that.

Now for my unwanted 2c :face_savoring_food:: Whether an explicit MSRV change counts as a semver break is really only something an ecosystem can agree on and unfortunately for you the consensus seems to be that it isn't - if you have to support being on an older compiler version but also want to be able to cargo update everything you're fighting the current. Adding resolver support is really just giving you a paddle, it can't make all those crates keep support for whichever version you're on in their newer versions.

Of course, that doesn't make you wrong, everyone has a weird environment in one way or another, but you shouldn't be particularly surprised that straying off the golden path makes things harder.

2 Likes

I was responding to a policy like

which is exactly what "currently only" MSRV policy means.

Yep, that's what I was saying further up about the meaning of SemVer depends on the community and even within a subset of a given community which is why corporations should take that into account when deciding whether or not a library is secure, maintainable, "production worthy", etc.

I think the reason so many never thought MSRV should matter as far as SemVer is concerned even though it's entirely opt-in is because the fix is minimal: upgrade your compiler. If the "fix" is "small enough", then it's not a "breaking change". Unfortunately that's not true for some environments where they have to keep using an "older" compiler; and even if one isn't affected by such environments, that doesn't make it not a problem. Fortunately the fix despite being 10 years into this whole mess was to make cargo smarter and attach SemVer semantics to MSRV which doesn't take much compilation time in contrast to my absurd example of having a tool that "fixes" a policy where the return type of a function changing is exempt from SemVer since that would seemingly require full compilation of a crate.

Also to be clear:

I'm not really affected by this. While one of my targets is OpenBSD, it is updated around every six months; and I actually jump through some hoops to get the newest version on it since I follow -stable and rustup does not support any OpenBSD platform. I do almost all my development on Arch Linux where I'm always on the "bleeding edge" including Rust, but that doesn't mean I don't think it's a problem. As time goes on and more crates eventually update MSRV to 1.84.0+, then this will thankfully all be a thing of the past.

1 Like

It is not because its small but that it is out of scope and impractical.

This is about more than MSRV but general build system requirements. If build system requirements were being tracked, then we'd likely be on Rust 23.5.0.

Its impractical on a couple of different fronts. If MSRV is a breaking change then libraries like serde could never update their MSRV. Not tracking MSRV in SemVer also means you can avoid more breaking changes so more intermediate libraries can have overlapping version ranges, reducing the chance of multiple versions being in your dependency tree, avoiding the associated problems with that, and allowing your users to have the flexibility of the choosing where in the continuum they set their MSR|V between old and new deps, rather than having hard cut offs.

I'd also recommend checking out SemVer Is Not About You

2 Likes

The configuration for the MSRV resolver was specifically set up so people can use it without bumping their MSRV, only requiring that they have access to a newer toolchain during development.