Pre-RFC: MSRV-aware resolver

As a library maintainer, I really don't understand why this proposal is pressuring me to do all my local development with the minimal-MSRV versions of all my dependencies rather than the latest version compatible with the cargo version I have installed. I set an MSRV so that users (and eventually automated tooling) can understand the absolute oldest rustc version the crate will work with. Not the rustc version I recommend using!

At very least, this proposal should have an escape hatch where cargo update defaults the current rustc version and cargo add defaults to the crate's MSRV

1 Like

A point against rust-version aware dep resolution by default: it's not all that uncommon for a crate with no features to satisfy its msrv, but have some optional features that pull in dependencies with higher msrvs and/or use features from later toolchains.

Which approach do you take for verifying your MSRV today?

And I would expect that if we go with my proposed change to the Pre-RFC to have a build.resolver.precedence field to override what we do for the resolver that it would not affect the version requirement generated for cargo add.

How not uncommon is that?

And note that this proposal is for controlling the preferred version things get resolved to. You would still be able to set the version requirement on an optional dependency to for a version with an MSRV higher than your own.

I check in a Cargo.lock.msrv and test with the MSRV's rustc in CI. Previously I just did the MSRV testing without any checked in lockfile, but the authors of some of my dependencies decided to stop cooperating with that

1 Like

Thanks for expanding on that. Getting a feel for the different approaches people take helps.

As someone who cares about MSRV and used a strict MSRV policy (MSRV bump = breaking change) for my libraries, I strongly disagree with this.

There is no other way to check whether specified rust-version is correct or not, but by testing it using the relevant toolchain either locally or using CI. Dependencies is only part of the equation, your code (including optional features) is also has to be MSRV-compatible.

IIUC your main concern is that developers may unintentionally use new features introduced in a new dependency version with a higher MSRV. But to me it looks like an issue around correctness of specified minimal dependency versions (i.e. crate foo depends on feature introduced in bar v1.1, but specifies bar = "1.0" in its Cargo.toml). This issue should be solved by testing with -Z minimal-versions not by MSRV-aware resolver.

You do not want to make -Z minimal-versions default on stabilization, do you? But you proposal for MSRV-aware resolver does effectively this, but only limited to Rust "dependency"!

I suggest to do the following:

  • If there is no Cargo.lock present, Cargo will resolve dependencies for the current version of toolchain (assuming crate's rust-version is lower or equal to the toolchain version).
  • cargo update will also resolve dependencies for the current version of toolchain.
  • Cargo.lock will include version of tolchain for which it was generated.
  • If Cargo.lock is present and Rust version specified in it is lower or equal to version of used toolchain, Cargo will use dependencies specified in it.
  • If Cargo.lock is present and Rust version specified in it is higher than version of used toolchain, Cargo will emit a warning and will re-generate Cargo.lock for the toolchain version.
  • Allow opt-in generation of Cargo.lock for an explicit Rust version and version specified in crate's rust-version using optional flags.
1 Like

Maybe slightly off-topic, but is the case from here (dependency non-semver-breaking update requiring an edition the locked rust toolchain version does not support) now handled?

Edition info has the property of being always present, and providing reliable if imprecise MSRV info. So, if it's not already the case, I think it's a source of info that can be leveraged to the maximum without concerns of not being always present or accurate.

2 Likes

It is still good to make noise that something is being done to fix a situation. I don't think I'd appreciate the compiler/tooling just fixing things for me when I might have an opinion on how it is being fixed (alternate lock file, changing dependency specifications, poking upstream, whatever).

FWIW, my project's CI solve this by having a generate-lockfile step before any build happen and then using --frozen --locked against a cache of downloaded Cargo.lock dependencies so that I know all CI jobs are using the same dependency resolution. The idea is to avoid job A and job B in the same pipeline compiling different dependency resolutions if rerun, say, a few hours apart.

Ed, re-sharing from our discussion so that it doesn't get lost. Thanks for your work on this! Regarding the policy that crate owners should follow with MSRV bumps, my preference would be for MSRV bumps to require at least a minor crate version bump.

This way older toolchains can still benefit from bug and/or security patches on the old MSRV via patch bumps on the old minor version.

Example:

  • 0.2.0 released with MSRV 1.64
  • 0.3.0 released with MSRV 1.66
  • 0.2.1 can be released later with a security fix, and can still be released with MSRV 1.64
2 Likes

I assume you mean

  • 1.2.0 released with MSRV 1.64
  • 1.3.0 released with MSRV 1.66
  • 1.2.1 can be released later with a security fix, and can still be released with MSRV 1.64

The 2 in 0.2 is equivalent to a major version in Cargo's 0.x.y semver extension.

3 Likes

@Nemo157 That might be a better example! I've used the major/minor terms loosely.

Perhaps it's off topic, but I thought it's closely related to an MSRV aware resolver. The point was about crate authors leaving room to release fixes on old MSRVs. Traditionally MSRV bumps are not considered breaking changes, but clearly they can be breaking for some users. This example hopefully demonstrates that there would be a benefit to leave room in the versioning scheme for patches to crate releases on older MSRVs.

We generally recommend MSRV changes be a minor incompatibility. However, there is little practical difference between MSRV minor and patch. cargo semver-check could offer some optional enforcement support for those wanting the gap for patching prior MSRVs.

1 Like

This is off topic, but I have wished several times that semver had versions "in between" every possible version number, by appending additional components. Need a version between 1.2.3 and 1.2.4? Use 1.2.3.1. Need a version between 1.2.3.1 and 1.2.3.2? Use 1.2.3.1.1.

This would make room for downstream patching, such as in Linux distributions or when vendoring a modified version. And it would allow preserving the property that the version number uniquely determines the contents, rather than having downstream modifications masquerade as the same version number.

5 Likes

As an update, #13056 makes it so cargo's docs explicitly recommend verifying MSRV

This is covered under future possibilities.

I don't want cargo publish filling in the package.rust-version field because I worry it will lead to problems down the road unless we separate intent from inferred values. At this time, the Index Summary does not include Edition. I think I'd prefer us adding an "inferred-rust-version` field to the Index Summary. However, I don't want to block this proposal on solving those problems which is why this is relegated to Future Possibilities.

I've updated the proposal to try to take into account everyone's thoughts (and those from today's cargo meeting), even if people won't always appreciate the place I landed on. I also filled in more sections of the template in preparation for posting this as an RFC.

The user also told us the presumably minimal compatible versions of project's dependencies. Why don't we plan to use -Zminimal-versions by default after its hypothetical future stabilization?

The same point applies to some other listed reasons.

And may significantly delay discovery of issues in new dependency versions which have bumped their MSRV above project's MSRV. Even worse, such issues will affect downstream users first, who are less likely to be able to properly diagnose such issues.

The "too-new" API problems which break MSRV of a dependency should be found and resolved directly by dependencies, not by downstream users. Correctness of minimal dependency versions should be separately checked using -Zminimal-version, MSRV-aware resolver is a wrong tool for this problem.

I believe the suggested approach should be developing with the latest stable (or even, in come cases, Nigthly) toolchain and check MSRV as part of CI, ideally, together with minimal dependency versions.

Even though it says cargo install is out of scope of this RFC, I do have one question: do you (or anyone else) have ideas for that?

Note: it doesn't have to be solved now, rather the question here is if there is a risk that a design decision at this point could limit further design decisions down the road for cargo install in a way we don't want.

I've written this with cargo-install in mind, including the config. The main thing holding it back is figuring out the user interactions. I don't see anything that would get in the way of this in the future.