Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimum Rust Version #201

Closed
mitsuhiko opened this issue Sep 22, 2022 · 53 comments
Closed

Minimum Rust Version #201

mitsuhiko opened this issue Sep 22, 2022 · 53 comments

Comments

@mitsuhiko
Copy link

The latest version of once_cell bumps up the minimum requirement up to 1.56 which is a huge jump. Is it conceivable to do a major version bump for this so that people who want to depend on it for libraries that wan to support older versions can stay on the old version?

@matklad
Copy link
Owner

matklad commented Sep 22, 2022

The short answer is no: per current policy, MSRV bump is not considered a semver breaking change. I want to avoid every reverse dependency having to change their Cargo.tomls (and their own major version, if they happen to expose once_cell via a public API).

After re-reading rust-lang/libs-team#72 couple of times, the policy I decided for once_cell is:


We support workflows which target up-to-date, supported versions of Rust compiler, but we also give somewhat generous grace period, as keeping perfectly up-to-date is hard.

We explicitly do not support workflows which depend on using the old compiler. Making sure that the latest once_cell can be compiled with rustc packaged with debian stable is a non-goal.

If users of once_cell find themselves with an outdated compiler, the following actions are suggested:

  1. Find a way to upgrade compiler
  2. If using old compiler is required, stick to old version of once_cell as well
  3. If a combination of old compiler and new once_cell is required, it's on the consumer to maintain a fork with backports.

The following considerations were the most salient for me:

  • Rust's own policy is essentially "live at head" -- only the latest rustc is officially supported
  • The number of people who are stuck with the old compilers is relatively small

An orthogonal policy is that MSRV bump is not considered semver breaking. There are two justifications for that:

First, this creates ripple effect over ecosystem, where each reverse-dependency has upload a new version with a different Cargo.toml. If the crate also happens to be part of the public API or pushes revdep's own MSRV, the reverse dependency is required to bump its major as well. Practically, not every rev-dep will notice new once-cell, so an extra burden falls on folks maintaining applications with Cargo.locks, who now need to chase their upstreams to avoid duplicate entries in Cargo.lock.

Second, bumping sevmer on MSRV makes the ecosystem less compatible with older compilers. Consider once_cell 1.0.0 with 1.42 as MSRV and kittens 1.0.0 which depends on once_cell = 1 and has it's own MSRV of 1.48. Now, once_cell bumps its MSRV to 1.56. The two scenarios are:

A: once_cell releases 2.0, kittens updates its dep to 2.0 and releases kittens 2.0 with MSRV of 1.56. Then, kittens releases 2.1 with a new API. A binary project rip_toys wants to use this new kittens API, so it upgrades to 2.1. Now, it's impossible to use the latest version of rip_toys on debian stable, because of MSRV.

B: once_cell releases 1.1. kitten does nothing: it still supports 1.48 when Cargo.lock contains once_cell = 1.0.0. When kittens adds a new API, it publishes 1.1.0. rip_toys uses the new API, but it keeps once_cell=1.0.0 in its Cargo.lock. As a result, newest version of rip_toys stays compatible with debian stable.

To rephrase this more mathematically, MSRV means that there exists a combination of dependencies which can be build with that compiler, not that any combination of dependencies can be build with it.

The exists formulation gives more freedom for actual users with Cargo.locks to pick their deps.


One practical problem with the above is that, if you naively test MSRV on CI, your CI might now be broken by upstream releasing a new version to crates.io. To solve this problem, the following rule is used:

MSRV CI: if you are not using the latest stable compiler, you must use Cargo.lock.

The suggested way to do this is to commit Cargo.lock.msrv file to the repo, and cp Cargo.lock.msrv Cargo.lock when running CI for MSRV. Here's how once_cell itself dose that:

{
let _s = section("TEST_MSRV");
let _e = push_toolchain(&sh, MSRV)?;
sh.copy_file("Cargo.lock.msrv", "Cargo.lock")?;
cmd!(sh, "cargo build").run()?;
}


Finally, there are three specific things anyone feeling strongly can do to improve the situation:

  • work on stabilizing Tracking Issue for once_cell rust-lang/rust#74465
  • teach cargo to use rust-version during version selection
  • write a gum & duct tape script which calls cargo update --precise && cargo check in a loop for creating Cargo.lock for old compilers without direct support for rust-version in Cargo.toml.

@mitsuhiko
Copy link
Author

Given that this is where the ecosystem is going, I will likely have to stop maintaining my own libraries for old rust versions.

teach cargo to use rust-version during version selection

I can't even imagine how this works in the resolver given the complexities of this. The resolver would have to back out a huge chunk of the dependency graph if it first encounters an incompatible version of a library.

I think the practical implication of this is that the community starts to restrict to test only against latest Rust again or maybe some quite recent version.

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this issue Sep 22, 2022
`hashbrown` depends on `ahash` which depends on `once_cell`. Sadly,
in matklad/once_cell#201 the `once_cell`
maintainer decided they didn't want to do the work of having an
MSRV policy for `once_cell`, making `ahash`, and thus `hashbrown`
require the latest compiler. I've reached out to `ahash` to suggest
they drop the dependency (as they could trivially work around not
having it), but until then we simply downgrade `hashbrown`.

`rust-bitcoin` also requires an older `hashbrown` so we're actually
reducing our total `no-std` code here anyway.
@matklad
Copy link
Owner

matklad commented Sep 22, 2022

One question I have here: today, once_cell's MSRV is described as "conservative" in readme and 11 months for me does sound conservative, though pretty close to the boundary. I'd like to get a rough "temperature reading" to use the words in a useful way:

Could 11 months old MSRV be called conservative?

  • 🚀 : if yes
  • 👀 if no

TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this issue Sep 22, 2022
`hashbrown` depends on `ahash` which depends on `once_cell`. Sadly,
in matklad/once_cell#201 the `once_cell`
maintainer decided they didn't want to do the work of having an
MSRV policy for `once_cell`, making `ahash`, and thus `hashbrown`
require the latest compiler. I've reached out to `ahash` to suggest
they drop the dependency (as they could trivially work around not
having it), but until then we simply downgrade `hashbrown`.

`rust-bitcoin` also requires an older `hashbrown` so we're actually
reducing our total `no-std` code here anyway.
TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this issue Sep 22, 2022
`hashbrown` depends on `ahash` which depends on `once_cell`. Sadly,
in matklad/once_cell#201 the `once_cell`
maintainer decided they didn't want to do the work of having an
MSRV policy for `once_cell`, making `ahash`, and thus `hashbrown`
require the latest compiler. I've reached out to `ahash` to suggest
they drop the dependency (as they could trivially work around not
having it), but until then we simply downgrade `hashbrown`.

`rust-bitcoin` also requires an older `hashbrown` so we're actually
reducing our total `no-std` code here anyway.
Kijewski added a commit to Kijewski/iana-time-zone that referenced this issue Sep 22, 2022
`once_cell` upgraded the MSRV to 1.56. This breaks the use of
`iana-time-zone` transitively even though we only use it for Android
targets.

This PR replaces `once_cell` by using `static mut` + `std::sync::Once`.
`once_cell` is more or less only a safe wrapper around both, but not
actually needed. We already do the same in our Windows targets.

Cf. <matklad/once_cell#201>
TheBlueMatt added a commit to TheBlueMatt/rust-lightning that referenced this issue Sep 23, 2022
`hashbrown` depends on `ahash` which depends on `once_cell`. Sadly,
in matklad/once_cell#201 the `once_cell`
maintainer decided they didn't want to do the work of having an
MSRV policy for `once_cell`, making `ahash`, and thus `hashbrown`
require the latest compiler. I've reached out to `ahash` to suggest
they drop the dependency (as they could trivially work around not
having it), but until then we simply downgrade `hashbrown`.

`rust-bitcoin` also requires an older `hashbrown` so we're actually
reducing our total `no-std` code here anyway.
@djc
Copy link

djc commented Sep 25, 2022

One question I have here: today, once_cell's MSRV is described as "conservative" in readme and 11 months for me does sound conservative, though pretty close to the boundary. I'd like to get a rough "temperature reading" to use the words in a useful way:

Could 11 months old MSRV be called conservative?

I think my answer would depend on whether 11 months is policy. Your long comment above to me suggests a more aggressive policy (on when we can expect MSRV updates) but as far as I can tell it doesn't actually specify when/how future updates would occur. I would agree your current choice of MSRV is (somewhat) conservative.

@GuillaumeGomez
Copy link
Contributor

GuillaumeGomez commented Sep 25, 2022

It's a shame that MSRV update is not part of the semver breaking changes as it completely broke my builds for sysinfo (as you can see in GuillaumeGomez/sysinfo#844). My two cents here would be that making a major release for MSRV change is better, especially for ecosystems where you stick to a precise rustc version.

@matklad
Copy link
Owner

matklad commented Sep 25, 2022

@GuillaumeGomez as per MSRV CI rule articulated above, ecosystems using precise rustc version could also use precise Cargo.lock to avoid any breakages.

@GuillaumeGomez
Copy link
Contributor

But that would prevent to automatically get any bugfix too. Tricky situation. 😆

@matklad
Copy link
Owner

matklad commented Sep 25, 2022

Applications generally use a lock-file anyway, so they already don't get automatic bugfixes.

For libraries, you want to run CI on stable without lockfile (to catch new upstream regressions) and on MSRV with lockfile (to catch MSRV/min-version regressions in your own code).

@dtolnay
Copy link

dtolnay commented Sep 25, 2022

@GuillaumeGomez I'd recommend https://github.com/dtolnay/rust-toolchain#toolchain-expressions instead of lockfile. This should be compatible with once_cell as implied by "we give somewhat generous grace period, as keeping perfectly up-to-date is hard" as long as the number of months/releases you pick for the CI build matches what once_cell would consider somewhat generous.

@GuillaumeGomez
Copy link
Contributor

No, I'll just enforce in my Cargo.toml to use the previous version so I can keep the current MSRV in sysinfo.

@djc
Copy link

djc commented Sep 25, 2022

Applications generally use a lock-file anyway, so they already don't get automatic bugfixes.

Except when installing through cargo install, which will default to resolving dependencies from scratch.

@dtolnay
Copy link

dtolnay commented Sep 25, 2022

No, I'll just enforce in my Cargo.toml to use the previous version so I can keep the current MSRV in sysinfo.

This is the worst approach that's been mentioned so far. This blocks anybody from using new sysinfo features together with new once_cell features, even if their compiler supports it.

@GuillaumeGomez
Copy link
Contributor

I don't see how blocking the once_cell version used in sysinfo is impacting anyone using sysinfo. When I'll make a new major release, I'll update the once_cell version alongside the MSRV and that's it. It's really problematic that a minor update can break your compilation.

Also I just realized that it was used in other dependencies of sysinfo, so basically I can't do anything about it in here... This is really bad... Why not bumping the medium version or something? Even with all your explanations, I don't understand how semver can allow this breakage inside minor versions. Doesn't make any sense...

So basically, I'm now forced to update MSRV version of sysinfo (and make a new major release) because a dependency decided to make a minor release with a breaking change.

To be clear: I'm not opposed to a MSRV change, I'm opposed to a silent MSRV change.

@dtolnay
Copy link

dtolnay commented Sep 25, 2022

I don't understand how semver can allow this breakage inside minor versions. Doesn't make any sense...

I can try to answer this. Semver very explicitly only applies to the documented public API. You first explain how your thing is intended to be used, and then everybody using it in that way can use the semver number to tell whether a particular update is potentially breaking.

A great number of observable things are not covered by semver because they are not documented as part of the intended way to use a crate. For example code that reaches into doc(hidden) private macro internals doesn't get to complain when those things change, because they are not public API. Similarly code that transmutes types with private members. Or code that assumes undocumented implementation details of layout, such as the precise size of a type in bytes.

In once_cell's case, the "public API" is that you build it with a recent enough compiler and stick to everything shown in the rendered rustdoc, and then once_cell's semver number will tell you which releases potentially break that usage.

@GuillaumeGomez
Copy link
Contributor

Thanks for the explanation. We all agree here that @matklad respected semver. What I'm complaining about here is that even though semver doesn't go over this, if the same code with no modification stop compiling because of a dependency, then it is a breaking change.

Well, I think the situation won't change and @matklad won't make a medium/major release over this so I'll just wait for a few days and then make a new major release for sysinfo like I said previously. Please just note that this is a very frustrating situation.

@dtolnay
Copy link

dtolnay commented Sep 25, 2022

I guess I will make my own sysinfo crate. :)

@GuillaumeGomez
Copy link
Contributor

Well, competition is always a good thing so go ahead. :)

@mitsuhiko
Copy link
Author

My take on the current situation is that we are at crossroads: there are two paths which might come out of this: there is a community inside Rust that wants to maintain older releases and will self select out of this, or that community won't arise.

Right now I'm not sure where it will fall. I would like to support older releases, users of my insta snapshotting tool would like support for older releases but unless that community forms, I don't have the energy myself to fight the windmills. I don't mind maintaining a fork of once_cell but I will very much mind maintaining a fork for every downstream dependency that also uses once_cell. So very likely in a few months I will have to make the call to either try to support this still or give up.

There is no pragmatic solution to the current problem. Lockfiles are completely useless for me as a library developer, the only work for the end user. So what happens now is that my libraries start breaking from one moment to another, from one pull request to another and I am regularly phased now with not having an answer to that other than to try to not use dependencies that have a written MSRV policy that exceeds or matches mine.

@Nemo157
Copy link

Nemo157 commented Sep 28, 2022

Lockfiles (or cargo give-me-lockfile-compatible-with-my-rustc) work just fine for libraries to maintain a CI job proving they have support for some MSRV with the dependency version requirements they impose. It does mean that application authors using such a library (if they require the MSRV) must also use cargo give-me-lockfile-compatible-with-my-rustc (or manually craft a lockfile via cargo update --precise) whenever they add or update a dependency, but that is a relatively small amount of work (once the authors understand what it is they need to do, the main barrier I see to this is there is not good documentation explaining how to maintain an MSRV in this way).

@mitsuhiko
Copy link
Author

@Nemo157 that's not a very useful guarantee to give from where I stand and also increases the complexity greatly for everybody involved.

@BurntSushi
Copy link

that's not a very useful guarantee to give from where I stand and also increases the complexity greatly for everybody involved.

I guess I don't really understand this. If you (as in the general "you") care about MSRV enough, then I don't understand why this isn't an acceptable price to pay. Someone has to pay the price somewhere. If you hold MSRV back, then you end up in a tangle like libc, and that has complexity costs too, because it can result in a nasty mess of conditional compilation.

@mitsuhiko
Copy link
Author

If you (as in the general "you") care about MSRV enough, then I don't understand why this isn't an acceptable price to pay. Someone has to pay the price somewhere.

Correct, and I would like to pay this price on behalf of my users for at least insta. Now I understand that this is an increasingly hard thing to do and I very likely will have to eventually stop doing this because my own dependencies are moving up.

Today I can publish a crate and declare a MSRV, but I cannot really guarantee it unless I also control all of my dependencies. Which means that I have to either let the user deal with this problem and publish some recommendations in the docs of known good upstream versions that support my MSRV. In that case they will have to manually cargo update --precise. Alternatively I can avoid dependencies with a much leaner MSRV policy than my own crate has.

Since someone needs to pay the price it seems to be easier for me (for now) to pick libraries that are more conservatives in MSRV bumps so that I am running less often into this issue.

There is however a good chance that the community as a hole is becoming less interested in supporting older rustc versions and then I will likewise stop caring about it. Right now I'm here waiting and seeing.

Another way to look at it is that the complexity of not using once_cell is less work for me than the alternatives proposed.

robinst added a commit to fancy-regex/fancy-regex that referenced this issue Sep 30, 2022
Some newer versions of transitive dev-dependencies don't work with the
minimum supported Rust version (1.42.0), so I used to pin them in
`Cargo.toml` whenever the MSRV build would break (tedious work). The
disadvantage of that is that older versions of those deps are used even
on newer Rust versions.

The latest breakage is matklad/once_cell#201,
but instead of pinning that to an older version too, this instead
introduces a `Cargo.lock` file that is only used for the MSRV build.

So newer Rust versions will continue to test against latest
dependencies, while the MSRV build should continue to work with the
older versions. One disadvantage with this is that consumers will need
to figure out their own `Cargo.lock` with precise versions that work
with older Rust versions, but that is expected to be a small number -
especially because in this case they are all dev-dependencies anyway.
robinst added a commit to robinst/linkify that referenced this issue Sep 30, 2022
Some newer versions of transitive dev-dependencies don't work with the
minimum supported Rust version (1.46.0), so I used to pin them in
`Cargo.toml` whenever the MSRV build would break (tedious work). The
disadvantage of that is that older versions of those deps are used even
on newer Rust versions.

The latest breakage is matklad/once_cell#201,
but instead of pinning that to an older version too, this instead
introduces a `Cargo.lock` file that is only used for the MSRV build.

So newer Rust versions will continue to test against latest
dependencies, while the MSRV build should continue to work with the
older versions. One disadvantage with this is that consumers will need
to figure out their own `Cargo.lock` with precise versions that work
with older Rust versions, but that is expected to be a small number -
especially because in this case they are all dev-dependencies anyway.
@eminence
Copy link

eminence commented Oct 1, 2022

@matklad Would you consider adding a section to the README that lists the current MSRV? This will be useful in the future when we need to find the MSRV of a specific version of once_cell. I know the MSRV is in the docs, but being in the README would make it visible on a page like crates.io

@matklad
Copy link
Owner

matklad commented Oct 1, 2022

I'd rather keep https://github.com/matklad/once_cell/blob/master/Cargo.toml#L7 as a source of truth.

@kornelski
Copy link

I'll drop my regular reminder that I'm gathering stats for ecosystem-wide MSRV here: https://lib.rs/stats#rustc

The current state is that MSRV < 1.56 is a waste of time.

The latest edition, just like the previous one, is a major cliff. Already over a quarter of all crates have upgraded to edition 2021 (or equally new features). Those crates haven't upgraded yet are mainly dead unmaintained ones. Out of actively maintained crates, a whooping 80% require 1.56+.

I don't think it's reasonably possible any more to develop a non-toy project that uses both up-to-date dependencies and Rust < 1.56. If you're spending effort on supporting old MSRV you're in the minority, and putting this all effort for an even smaller minority.

MSRV has an incredibly "viral" negative network effect. The worst-MSRV-wins effect means that even if 99% of your dependencies try to support old Rust, and 1% doesn't, then your project and everyone using is still 100% broken.

@matklad
Copy link
Owner

matklad commented Oct 22, 2022

I think the discussion has run its course here, closing!

I've clarified the exact supported range in #204, but otherwise the policy articulated in the first comment remains unchanged!

@matklad matklad closed this as completed Oct 22, 2022
cardoe added a commit to cardoe/stderrlog-rs that referenced this issue Nov 4, 2022
Added a Cargo.lock file for MSRV support to pin once_cell back to 1.14.0
artifically with `cargo update -p once_cell --precise 1.14.0` since it
broke MSRV compat. See matklad/once_cell#201 for details and why it'll
remain broken.
cardoe added a commit to cardoe/stderrlog-rs that referenced this issue Nov 4, 2022
Added a Cargo.lock file for MSRV support to pin once_cell back to 1.14.0
artifically with `cargo update -p once_cell --precise 1.14.0` since it
broke MSRV compat. See matklad/once_cell#201 for details and why it'll
remain broken.
optout21 pushed a commit to optout21/rust-lightning that referenced this issue Jul 24, 2023
`hashbrown` depends on `ahash` which depends on `once_cell`. Sadly,
in matklad/once_cell#201 the `once_cell`
maintainer decided they didn't want to do the work of having an
MSRV policy for `once_cell`, making `ahash`, and thus `hashbrown`
require the latest compiler. I've reached out to `ahash` to suggest
they drop the dependency (as they could trivially work around not
having it), but until then we simply downgrade `hashbrown`.

`rust-bitcoin` also requires an older `hashbrown` so we're actually
reducing our total `no-std` code here anyway.
Stargateur added a commit to Stargateur/once_cell that referenced this issue Aug 21, 2023
I think that good to put MSRV in the README.

Fix somehow matklad#201
This was referenced Aug 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests