Rust version requirement change as semver breaking or not


#1

I’m aware that there have been two schools of thought regarding whether an increase in the minimum Rust version that a crate builds with should be treated as a semver breaking change for the crate.

The root cause of the problem is, of course, that the Rust version itself is not part of the cargo dependency constraints, so cargo won’t hold back upgrades that don’t suit the Rust version in use.

I’m aware of the arguments in both directions: On one hand, having cargo update (without rustup update) break the build is, for practical purposes, a breaking change. On the other hand, it’s bad for the ecosystem to accommodate users of Rust who don’t update Rust itself, since semver-breaking changes in the crate version number split the library ecosystem and also waste people’s time by causing a chain of semver-breaking version changes throughout the library ecosystem when other libraries depend on a library whose Rust version requirement moved forward.

Now that some time has passed since this was last debated without consensus, has consensus emerged? That is, is there now a clear community norm whether to treat Rust dependency increases as semver-breaking for a crate?


#2

At the current time, I don’t think there is a strong consensus. However, I do believe there are some plans for the path forward. IIRC, they sprung out of the post-poned RFC for LTS releases and a different RFC addressing minimum supported Rust specifically. @aturon then outlined some ideas for moving forward here: http://aturon.github.io/2018/07/25/cargo-version-selection/

If I had to guess at the pulse, I think I’d say that our current trajectory is towards a solution that involves Cargo becoming aware of the minimum supported Rust version, but I don’t have any good feeling of how strong that trajectory is.

In terms of practice, this is what I currently do:

  • For crates that I maintain that are 1.0 or greater, I do my best to be conservative with the minimum supported Rust version. However, I specify a policy that permits bumping the minimum supported Rust version in minor version releases. So a 1.1 release could require a newer Rust than a 1.0 release. This is somewhat of a compromise, and I’m not sure it’s a very good one either. Example: https://github.com/rust-lang/regex#minimum-rust-version-policy
  • For crates at 0.x, I also try to do a minor version release when bumping the minimum Rust version. In this case, it’s a semver bump as opposed to the aforementioned strategy for 1.0 crates which is not a semver bump.
  • For applications, I’ve switched to “requires the latest stable release of Rust, but all patch releases will compile on the same version of Rust that its corresponding minor version compiled on.” I tried to be more conservative about this for a while and even succeeded, but once I switched to crossbeam-channel, ripgrep grew a whole new mess of dependencies that made it completely intractable for me to stay on top of this. So I gave up after consulting interested parties: https://github.com/BurntSushi/ripgrep/issues/1019

I do not think the current situation is sustainable. Even though I specifically watch for stuff like this in most of my dependencies, I still miss things. The only way it’s going to get fixed IMO is either with a very strong community rallying point (LTS releases or @aturon’s “shared policy”) or with tooling (@aturon’s “stated toolchain”). In particular, many of the more widely used crates that I maintain are quite conservative and aren’t seeing major internal changes, so there’s really no strong impetus to use new language features. On occasion, new features come along that I want to use, I’ve ended up achieving that via version sniffing and conditional compilation. It’s not ideal and certainly cannot be achieved in every case. For crates that are moving more quickly or want to take advantage of new language features, a conservative MSRV policy is probably quite annoying and/or difficult to adhere to.


#3

I think implementing RFC 2495 fully (with version taken into account during deps resolution) is the best answer. This way you can have your cake and eat it: nobody’s build will break if you start requiring a newer version of Rust at any time.

Semver-major bump is disruptive, and causes a transition period when the library gets duplicated in user’s dependency tree and/or its interfaces aren’t compatible across libraries. It’s not a pain I want to subject users regularly to, so I support only latest(ish) stable and break old Rust versions without remorse.

If Rust had LTS I could think twice before breaking LTS compatibility of “finished” crates, but I probably just wouldn’t support LTS versions for new crates in development.


#4

Thanks. Based on this, I published encoding_rs 0.8.11 as semver non-breaking. I didn’t get semver complaints. (It did turn out that I had accidentally broken clippy-compat on stable by following the guidance given by nightly clippy, so 0.8.12 reverted that bit.)

I hope there won’t be an LTS. Not having an LTS but having compat with old code for real has lead to Rust not getting stuck for the lifetime of an RHEL release. It would be such a strategic blunder to introduce an opportunity for stagnation now that Rust has gotten this far without it.


#5

FWIW, I did notice. :slight_smile: I just don’t have the energy to really pursue it any more. I think as long as the MSRV is in a CI config somewhere, then that’s good enough for now until we get better tooling (or ecosystem) support.