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

Reduce our MSRV as much as possible #554

Open
2 tasks
joshlf opened this issue Oct 27, 2023 · 2 comments
Open
2 tasks

Reduce our MSRV as much as possible #554

joshlf opened this issue Oct 27, 2023 · 2 comments
Labels
experience-hard This issue is hard, and requires a lot of experience help wanted Extra attention is needed

Comments

@joshlf
Copy link
Member

joshlf commented Oct 27, 2023

Our MSRV is currently 1.61, which is higher than the MSRVs of many crates. Here are MSRVs of the subset of the top 100 most-recently-downloaded crates that contain unsafe code:

Top 100 crates
Crate MSRV Unsafe
syn 1.56 search
bitflags 1.56 search
hashbrown 1.63 search
regex-syntax 1.65 search
proc-macro2 1.56 search
quote 1.56 search
base64 1.48 search
libc 1.13 search
unicode-ident 1.31 search
serde 1.31 search
cfg-if unknown search
rustix 1.63 search
rand_core unknown search
serde_derive 1.56 search
aho-corasick 1.60 search
indexmap 1.63 search
itoa 1.36 search
regex-automata 1.65 search
rand unknown search
getrandom 1.36 search

We should work to reduce our MSRV as much as possible so that more crates can replace unsafe code with zerocopy.

Mentoring instructions

  • Repeat the following steps as many times as possible:
    • Determine what is preventing our MSRV from being one version earlier
    • Determine whether there's a workaround for the same behavior that is compatible with an earlier MSRV

Conditional compilation problems

One problem with lowering our MSRV is that some features we want to use were introduced in more recent Rust versions. Naively, one might expect that this makes lowering our MSRV dead in the water. However, it's possible to work around this, albeit with a lot of machinery.

The core insight is that it's okay for zerocopy to provide certain features only when compiled with certain Rust versions. For example, imagine that a certain feature is only available on 1.60 and above. Downstream crates can be in one of two buckets:

  • Their MSRV is 1.60 or above. They will never compile with Rust <= 1.60, and so they will never compile zerocopy without support for this feature.
  • Their MSRV is below 1.60. They (hopefully) test their crate with their MSRV, and so are unable to publish code which makes use of this feature (unless they use their own conditional compilation logic, which just kicks the can to their downstream crates).

In both cases, it's consistent with semver rules for us to enable certain features only on more recent Rust versions.

This brings us to our first requirement: We need to support detecting the Rust toolchain version at compile time.


If we take this approach, it introduces a new problem: now zerocopy's behavior can differ by toolchain. If we continue to only test on MSRV, stable, and nightly toolchains, then there will be some toolchain versions which introduce different behavior relative to their parent, but on which we don't test. For example, if our MSRV is 1.56, and our pinned stable toolchain is 1.70, but we have a feature that is enabled on versions after 1.65, then it's possible that that feature is buggy on 1.65, 1.66, 1.67, 1.68, and 1.69. This implies that we also need to test on 1.65 (the first Rust version for which the feature is enabled). We need to do this for any Rust version for which we enable new behavior.

This brings us to our second requirement: In CI, we need to test on every Rust version which introduces new behavior.


This introduces another problem, though. How do we actually implement this? Naively, we could write our build.rs script to perform toolchain detection based on hard-coded toolchain versions, and then separately update our CI configuration to test on these toolchain versions. However, this risks bit rot: we could easily change the hard-coded versions in either place, but forget to update them in the other place. There would be no automatic way to detect this.

Instead, we should have one source of truth for the list of toolchain versions. This brings us to our third requirement: Both build.rs and our CI scripts should parse a single source of truth for the list of toolchain versions.


All three of these requirements are implemented in #843.

@joshlf joshlf added help wanted Extra attention is needed experience-hard This issue is hard, and requires a lot of experience labels Oct 27, 2023
joshlf added a commit that referenced this issue Oct 27, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 27, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 27, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 27, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 27, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 28, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 28, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 28, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 29, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 29, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 29, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
github-merge-queue bot pushed a commit that referenced this issue Oct 29, 2023
Modify the way that certain memory safety properties are validated in
our `transmute!`, `transmute_ref!`, and `transmute_mut!` macros to allow
that code to compile on 1.60.

Makes progress on #554
joshlf added a commit that referenced this issue Oct 31, 2023
Makes progress on #554
@polarathene
Copy link

I'm a little curious about the impact of this (as a general goal, not specifically the impact to zerocopy).

  • As you raise PRs to iteratively lower the MSRV, you need to make more sacrifices that affects the maintenance of zerocopy?
  • And then eventually raising MSRV again to undo these changes?

Perhaps it's more feasible for zerocopy due to minimal dependencies, so most of the concerns below can be disregarded 😅


In #557 you reference libc MSRV and targets of 1.30 (8% compatible) and 1.31 (19% compatible), with MSRV compatibility of current crates.io ecosystem:

image

Your current target reached (1.57) is 59% compatible:

image

Once you pass 1.56.0 you've lost support for depending on any crates using edition = 2021, while some crates for example require 1.60 due to usage of new Cargo.toml syntax introduced then but those releases are completely invisible/ignored by the resolver on earlier toolchains it seems. The rust-version field was also introduced in 1.56.0 (which I see zerocopy leverages).

In contrast of that full ecosystem, the lib.rs stats link also shows the MSRV compatibility of more recently updated crates:

image

Note the widening "broken deps" compatibility, along with the steep "incompatible" increase after going below edition = "2021" compatibility with 1.55.

Possibly relevant: mitsuhiko/insta#289 (comment)

syn bumped its MSRV up to 1.56 with the v2 release. This alone will bump the MSRV of a lot of projects to at least 1.56 once updates propagate out

I don't think we depend on syn with all features off, but any feature requiring serde will pull it in with a non-semver breaking change


Until the MSRV resolver work is stabilized and crates adopt declaring rust-version to better communicate compatibility through the dependency chain, the further back in toolchain releases you go the more problems you run into resolving indirect dependencies.

What once resolved and installed correctly via Cargo.toml fails on the aging toolchain as time marches forward (rust-version does not avoid that issue unless all dependencies adopt it). Managing a Cargo.lock becomes a point of friction, especially if starting a new project with toolchains from 8 years ago (assuming the environment to support requires it).


What will your plan be once you reached the MSRV target you're aiming for?

Will you continue to support that for N years? Semver bumps are apparently advised to not be affected by MSRV bumps for valid reasons, so using minor version bumps for MSRV and keeping those release streams updated with patch releases doesn't appear to be advised? (crates like tokio appear to do this, but due to major version of 1 instead of 0, that is viable AFAIK)

While the MSRV policy resolver work should improve on this situation, by the time crates have more widely adopted declaring rust-version it's not likely to have a baseline that satisfies very old toolchain releases.

  • When the resolver isn't able to identify a compatible rust-version, the last release without one that matches the semver range is selected but likely won't be too far from the last known rust-version declared and thus fail.
  • Take hashbrown 0.14.2 for example. It declares rust-version = "1.63.0", but will require 1.64.0` by default.
    • You need to opt-out of a feature or Cargo.lock pin the allocator-api2 dependency to a compatible version.
    • Even if that dependency later adopts a rust-version, the earlier releases without it would still resolve for the older toolchains.
    • Likewise that dependency can satisfy an MSRV of 1.56.0 by minimizing it's own dependency on once_cell to the lower semver range 0.15.0, but that sets a hard limit due to edition = "2021" usage.
  • Earlier hashbrown releases had rust-version = "1.56.0" but again due to a dependency release after hashbrown releases, the compatible crates in semver had been yanked (ahash), while newer point releases implicitly bumped MSRV to 1.60.0 due to Cargo.toml syntax change that older tool chain could not parse so nothing was resolvable for earlier toolchains. Depending on when you installed / resolved the ordered-multimap = "0.4" package semver, you might have had a newer release, or today you'd be reverted back to 0.4.0 (unless attempting to use update -Z msrv-policy, which resolves newer crates but fails to install).

You'd have a better idea of all this I assume, along with actual users requiring it? ( #557 is marked with the label customer-request)

With Rust 1.31 (introduces edition = "2018"), and LTS distros from that year using that or older, some with LTS support windows of 10 years, I assume that could avoid a bump until around 2028?

  • Does zerocopy stop maintaining MSRV compatibility with releases before then?
  • Is the goal to just reach a low MSRV, and then bring up the MSRV again with rust-version usage?
  • Or adopt a 1.0.0 major release to introduce MSRV bumps via minor releases and allow patch releases to those older MSRV minors similar to tokio?
  • Some other plan to maintain MSRV compatibility with support for backports of fixes?

@joshlf
Copy link
Member Author

joshlf commented Oct 31, 2023

Thanks for the detailed analysis! I think I can answer most of your questions, but let me know if I've missed anything:

  • Our current MSRV policy is just that we consider an MSRV bump to be a semver-breaking change. We don't promise anything about when we'll make such changes, other than promising that they'll only happen in semver-breaking releases (e.g., 0.7.X -> 0.8.0).
    • For users who are relying on zerocopy but not exposing it in their API, we expect them to be able to stay on the old version train if the new MSRV is problematic, or to upgrade if they are compatible with the new MSRV.
    • For users who are relying on zerocopy and exposing it in their API, they already need to handle other (non-MSRV-related) breaking changes, e.g. by providing one feature per zerocopy version, and MSRV would not introduce a new concern that didn't already exist with other API changes introduced in the new version.
  • The libc issue refers to increasing libc's MSRV to 1.31, but we don't intend to decrease our MSRV to 1.31. Instead, the thinking there is that increasing libc's MSRV to 1.31 would make it easier to support an optional zerocopy feature. That feature would require a higher MSRV than 1.31 (namely, whatever zerocopy's MSRV is).
  • We don't have any concrete plans to reach 1.0 (we intend to eventually, but we aren't currently planning for it concretely), and we don't have any particular thoughts on whether or how our MSRV policy will change at that point.

joshlf added a commit that referenced this issue Nov 2, 2023
In order to do this, we make a few changes:
- In 1.56, panicking is not supported in `const fn`s. In order to
  support 1.56 while still panicking in `const fn`s in later versions,
  we make a few changes:
  - We introduce a `build.rs` script which detects the Rust toolchain
    version and emits the cfg `zerocopy_panic_in_const_fn` if the Rust
    version is at least 1.57.
  - In some `const fn`s, we put debug assertions behind
    `#[cfg(zerocopy_panic_in_const_fn)]`.
  - In some `const fn`s, we replace `unreachable!()` with non-panicking
    code when `#[cfg(not(zerocopy_panic_in_const_fn))]`.
  - In one case, we compile a method as either a `const fn` or a
    non-`const fn` depending on `zerocopy_panic_in_const_fn`.
- We replace `&*dst` with `transmute(dst)`.
- We make sure that, in `impl` blocks, `const` parameters always come
  last.
- We make the `byteorder` feature/crate dependency off by default. This
  is a breaking change, and so we bump the version number to
  0.8.0-alpha.

Release 0.8.0-alpha.

Makes progress on #554
@joshlf joshlf mentioned this issue Dec 4, 2023
65 tasks
joshlf added a commit that referenced this issue Jan 24, 2024
joshlf added a commit that referenced this issue Jan 24, 2024
TODO: Populate commit message with details about updated infrastructure
in this commit (notably, testutil crate's use of MSRV vs MWRV and CI's
passing of --ignore-rust-version)

Makes progress on #554
joshlf added a commit that referenced this issue Jan 25, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Jan 25, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Jan 25, 2024
Makes progress on #554
github-merge-queue bot pushed a commit that referenced this issue Jan 25, 2024
joshlf added a commit that referenced this issue Jan 26, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 7, 2024
In order to lower our MSRV as far as we'd like, we will be losing access
to some features that we currently use. Some of these features cannot be
simply worked around, and impose limitations on what APIs we can support
(for example, what trait bounds can exist on `const fn`s).

This commit works around this problem by introducing a general-purpose
framework for detecting the toolchain version at compile time, and
conditionally compiling code based on whether or not the toolchain is
recent enough. This ensures that no user code will break. In particular,
at any given time, if our MSRV is version N, then all code we publish is
compatible wtih version N. If, later, we lower our MSRV to M < N, we can
ensure that all functionality from the old version of zerocopy is still
present when compiling on versions at least as recent as N. We know that
none of our old users had an MSRV lower than N since they were using
zerocopy with an MSRV of N. Thus, publishing a new version in which some
features are not available on versions lower than N is not problematic
for existing users.

This approach introduces a new degree of freedom that we need to be sure
to test in CI. Previously, in CI, we tested on the MSRV, stable, and
nightly toolchain versions. Now, our behavior can differ on an arbitrary
number of toolchains, and we need to make sure to test on every such
toolchain. To accomplish this, we put the source of truth for these
versions in `Cargo.toml`. We introduce a `build.rs` file which parses
these versions and emits `--cfg` variables which can be used by our Rust
code. We update `cargo.sh` to be able to make use of these versions, and
add every such version as a toolchain to test in CI. Finally, we add a
CI job which parses `Cargo.toml` and `.github/workflows/ci.yml` to
ensure that every version listed in `Cargo.toml` is tested in CI.

In order to test this framework, this commit lowers our MSRV to 1.58.0.

Makes progress on #554
github-merge-queue bot pushed a commit that referenced this issue Feb 7, 2024
In order to lower our MSRV as far as we'd like, we will be losing access
to some features that we currently use. Some of these features cannot be
simply worked around, and impose limitations on what APIs we can support
(for example, what trait bounds can exist on `const fn`s).

This commit works around this problem by introducing a general-purpose
framework for detecting the toolchain version at compile time, and
conditionally compiling code based on whether or not the toolchain is
recent enough. This ensures that no user code will break. In particular,
at any given time, if our MSRV is version N, then all code we publish is
compatible wtih version N. If, later, we lower our MSRV to M < N, we can
ensure that all functionality from the old version of zerocopy is still
present when compiling on versions at least as recent as N. We know that
none of our old users had an MSRV lower than N since they were using
zerocopy with an MSRV of N. Thus, publishing a new version in which some
features are not available on versions lower than N is not problematic
for existing users.

This approach introduces a new degree of freedom that we need to be sure
to test in CI. Previously, in CI, we tested on the MSRV, stable, and
nightly toolchain versions. Now, our behavior can differ on an arbitrary
number of toolchains, and we need to make sure to test on every such
toolchain. To accomplish this, we put the source of truth for these
versions in `Cargo.toml`. We introduce a `build.rs` file which parses
these versions and emits `--cfg` variables which can be used by our Rust
code. We update `cargo.sh` to be able to make use of these versions, and
add every such version as a toolchain to test in CI. Finally, we add a
CI job which parses `Cargo.toml` and `.github/workflows/ci.yml` to
ensure that every version listed in `Cargo.toml` is tested in CI.

In order to test this framework, this commit lowers our MSRV to 1.58.0.

Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
github-merge-queue bot pushed a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 8, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 9, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 9, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 9, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 9, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 10, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 10, 2024
Makes progress on #554
joshlf added a commit that referenced this issue Feb 10, 2024
Makes progress on #554
github-merge-queue bot pushed a commit that referenced this issue Feb 10, 2024
Makes progress on #554
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experience-hard This issue is hard, and requires a lot of experience help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants