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

RFC: Minimum Supported Rust Version #2495

Merged
merged 19 commits into from
Oct 10, 2019
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions text/0000-min-rust-version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
- Feature Name: min_rust_version
- Start Date: 2018-06-28
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

Add `rust` field to the package section of `Cargo.toml` which will be used to
specify crate's Minimum Supported Rust Version (MSRV):
```toml
[package]
name = "foo"
version = "0.1.0"
rust = "1.30"
```

# Motivation
[motivation]: #motivation

Currently crates have no way to formally specify MSRV. As a result users can't
check if crate can be built on their toolchain without building it. It also
leads to the debate on how to handle crate version change on bumping MSRV,
conservative approach is to consider such changes as breaking ones, which can
hinder adoption of new features across ecosystem or result in version number
inflation, which makes it harder to keep downstream crates up-to-date. More
relaxed approach on another hand can result in broken crates for user of older
compiler versions.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

`cargo init` will automatically create `Cargo.toml` with `rust` field equal to
`rust="stable"` or `rust="nightly: *"` depending on the currently used toolcahin.
On `cargo publish` cargo will take currently used Rust compiler version and
will insert it before uploading the crate. In other words localy your `Cargo.toml`
willl still have `rust="stable"`, but version sent to crates.io will have
`rust="1.30"` if you've used Rust 1.30. If "nightly: \*" is used, then `cargo`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this kind of automatic conversion from stable to e.g. 1.30. I think it is counter-intuitive.

Instead, I would like to propose a slightly more complicated approach with a huge effect on the question raised above (namely "Is a MSRV change a breaking change for a crate which then requires a major version bump?"). Let me give an example:

  1. A user might declare the MSRV to be 1.30. This means that the crate which is published to crates.io can be compiled by every compiler between 1.30 (inclusive) and 2 (exclusive), just as semantic versioning suggests. If he uploads the next (patch or minor) version of the same crate, the MSRV must be equal to or smaller than 1.30. Everything else would be considered a breaking change. If he uploads the next major version, of course he's free to change the MSRV.

  2. A user might also declare the MSRV to be stable. When published to crates.io, stable is converted to stable(1.30) with 1.30 being the current stable release. This means that the published artifact will work with a compiler between version 1.30 (inclusive) and 2 (exclusive). So far no difference! Now, times passes and a new stable compiler (1.31) was published. The crate artifact which was already published still works with a compiler between 1.30 (inclusive) and 2 (exclusive). But if the crate author opts to publish a new crate version, the MSRV for the new artifact is automatically raised to stable(1.31) which means that it guarantees to work with a compiler version between 1.31 (inclusive) and 2 (exclusive). This MSRV change wound not be considered a breaking change since it was already declared before.

Therefore a crate author has to options:

  1. Fix the MSRV to a specific version, e.g. 1.30.
  2. Fix the MSRV to stable which means that a future crate version might require a future compiler version (which is at least stable).

Anyway, changing the MSRV is considered to be a breaking change which requires a major version bump. (Someone might relax this requirement as I did in the example above and allow a change from e.g. stable to 1.32 or 1.31 to 1.30, since that is no compatibility problem).

I think, one could take this idea even further and allow labels like stable - 0.4 which means that, a future crate works with the current version minus 4 minor versions. I think this is even a reasonable alternative to LTS releases as proposed in #2483. (Or we could then say that lts is stable minus one year or another reasonable time considered to be a long term...)

Copy link

@teiesti teiesti Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, this idea does not work for nightly because the nightly compiler does not guarantee backward compatibility.

Copy link
Contributor Author

@newpavlov newpavlov Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably my wording was not clear enough, but I've meant almost exactly the same behaviour as was described by you. Automatic conversion from stable to 1.30 happens if user executes cargo publish with 1.30 toolchain, in his local Cargo.toml rust field will be left unchanged, equal to stable. After that user makes some changes to the crate and updates toolchain to stable 1.33. Now on cargo publish for the next crate version stable will be converted to 1.33, which will mean >=1.33.0, <2.0.0.

For nightlies I currently propose to use nightly: * as a default option for crates which require nightly compiler, which will mean "any Nightly version".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for LTS releases, they are not only about MSRV, they will also serve as a synchronization point for crate authors and package authors (think Debian). LTS also means that crucial updates (e.g. security or soundness fixes) will be backported to those versions. So I don't think that stable - 0.4 will be able to cover all LTS use-cases.

Copy link

@teiesti teiesti Jul 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably my wording was not clear enough, but I've meant almost exactly the same behaviour as was described by you.

To be honest, I am still not sure that we are talking about the same thing: I think, the crucial difference is that in the first case (rust = 1.30) the published MSRV on crates.io is 1.30, while in the second case (rust = stable), the published MSRV is stable(1.30). To be even clearer: 1.30 is not at all equal to stable(1.30). While they behave equal when it comes to the crate's current version, they are different for the next (patch or minor) version: In the first case the MSRV will be 1.30, while in the second case it might be 1.30 or stable(1.31) or even 1.31. Therefore, the crate user can determine from the difference between 1.30 and stable(1.30) how the crate author answers the question "Is an MSRV change a breaking change?". In the first case, the answer is yes, in the second case it is no!

I would like to see a whole passage dedicated to that matter which leaves no room for interpretation... ;-)

I would also like to see a passage dedicated to "version arithmetic" which covers e.g.:

  • Under which circumstances is a MSRV downgrade/upgrade a breaking change?
    • 1.30 can be 1.29, 1.28.1, ... in the next crate version.
    • stable(1.30) can be 1.29, 1.28.1, ... or stable(1.30.1), stable(1.31), ... or 1.30, 1.30.1, 1.31, ... in the next crate version (as long as the rustc is already stabilized).
  • What if crate A depends on crate B and crate C. What are the requirements for the MSRV of crate A given the MSRV of B and C?
    • Something like MSRV(A) <= min(MSRV(A), MSRV(B)). We need a (partial?) order here!
  • (How) is it possible to calculate with MSRVs? Examples:
    • stable(1.30.1) - 0.0.1 = stable(1.30)
    • stable(1.30) - 0.3 = stable(1.27)
    • stable(1.30.1) - 0.0.2 = stable(1.30.0) Does this even work?
    • stable(1.30) - 1 = undefined

As for LTS releases, they are not only about MSRV, they will also serve as a synchronization point for crate authors and package authors (think Debian). LTS also means that crucial updates (e.g. security or soundness fixes) will be backported to those versions.

You are right. But I highly doubt the utility of LTS releases for other cases than language version requirements:

  • Take the argument about patches that are backported to LTS. If such a package becomes necessary, Debian (or anyone else) will need to adopt the fixed LTS version. In that case, they can also adopt the current stable version, since Rust is guaranteed to be backward-compatible (in version 1.x). LTS releases are even counterproductive in this use case, since they encourage not upgrading to the current release and therefore increase the number of old compilers out there.
  • Take the argument about synchronization points. We already have them, they are called "editions". (We could also publish guideline that give suggestion when a good times has come to update and how long crates should support old compiler versions. This could be done in an RFC which depends on things like stable(1.30) - 0.2.)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've re-read your RFC and I think now that You've mentioned some of my points. But perhaps, they are still useful to clarify things... ;-)

Btw Thanks for Your effort!

Copy link
Contributor Author

@newpavlov newpavlov Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't quite get stable(x) functionality then. The only difference which I can see is that stable(1.30) will hint that rust field was automatically inserted by cargo, otherwise behavior looks similar to me. I'll try to describe current proposal a bit better in a separate PR to my repo. I'll link it later and I will be happy to hear your comments on which parts you think will need additional clarification.

how the crate author answers the question "Is an MSRV change a breaking change?"

As I see it, if public API does not change, then MSRV change will never (well, except the initial migration to using rust field) be a breaking change, be it upgrade or downgrade. Dependency versions resolution algorithm will handle a selection of appropriate versions for current toolchain automatically.

What if crate A depends on crate B and crate C. What are the requirements for the MSRV of crate A given the MSRV of B and C?

Don't forget that dependency can be defined as 0.x, so you have not one version, but a set. Thus formula will be MSRV(A) >= max(min( [MSRV(A1), ..MSRV(An)] ), min( [MSRV(B1), ..MSRV(Bn)] )). For stable versions order should be quite simple and you even don't have to calculate it explicitly. Running cargo publish with your MSRV toolchain will automatically check if non-empty solution for dependency versions exists.

But with "nightly versions" extension things become more complex. You'll have to check that your MSRV condition contains only such nightly versions which can be covered by all of your dependencies.

(How) is it possible to calculate with MSRVs?

I am not sure this feature will pulls its weight. Plus it can be added later in a backwards compatible way by a separate proposal.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about the theoretical foundations of my proposal and I came up with this article. It is some kind of pre-work for my proposal in this thread but I think its worth sharing here. (Anywhere, I'll try to write another article surrounding these ideas soon.)

An internals thread for a discussion about the article can be found here.

will not select current Nightly version, but will assume that cratecan be built
with all Nightly versions.

In case if you have `rust="stable"`, but execute `cargo publish` with Nightly
toolcahin you will get an error. Same goes for `rust="nightly: *"` which can be
published only using nightly toolchain.

If you are sure that your crate supports older Rust versions (e.g. by using CI
testing) you can specify this version explicitly, e.g. `rust="1.30"`.
On `cargo publish` it will be checked that crate indeed can be built with the
specified version, i.e. the respective toolchain will have to be installed on
your computer.

By default toolchain check is disabled for `cargo publish`, `cargo check` and
`cargo test`, but it can be enabled with `--check-msrv-toolchain` option.
To disable this check for `cargo publish` you can use `--no-verify`option.

The value of `rust` field (explicit or autmatically selected by `cargo`) will
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
be used to determine if crate can be used with the crate user's toolchain and
to select appropriate dependency versions.

For example, lets imagine that your crate depends on crate `foo` with 10 published
versions from `0.1.0` to `0.1.9`, in versions from `0.1.0` to `0.1.5` `rust`
field in the `Cargo.toml` sent to crates.io equals to "1.30" and for others to
"1.40". Now if you'll build your project with e.g. Rust 1.33, `cargo` will select
`foo v0.1.5`. `foo v0.1.9` will be selected only if you'll build your project with
Rust 1.40 or higher. But if you'll try to build your project with Rust 1.29 cargo
will issue an error.

Note that described MSRV constraints and checks for dependency versions resolution
can be disabled with `--no-msrv-check` option.

`rust` field should respect the following minimal requirements:
- value should be equal to "stable", "nightly: \*" or to a version in semver format.
Note that "1.50" is a valid value and implies "1.50.0". (also see "nightly versions"
extension)
- version should not be bigger than the current stable toolchain

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the current nightly toolchain? Because during the lifetime of Rust 1.xx stable, 1.xx+2 is the nightly version in use. This would then disallow the publishing to those crates to crates.io if it checks against the current stable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I meant stable. If we have stable Rust 1.35 and nightly 1.37, using rust="1.37" implying nightly toolchain will result in an ambiguity when stable Rust 1.37 will be published. So if crate depends on Nightly features, then it will have to use rust="nightly"/"nightly: ...".

- version should not be smaller than 1.27 (version in which `package.rust` field
became a warning instead of an error)

`rust` will be a required field. For crates uploaded before introduction of this
feature 2015 edition crates will imply `rust="1.0"` and 2018 edition will imply
`rust = "1.30"`.

It will be an error to use `rust="1.27"` and `edition="2018"`, but `rust="1.40"`
and `edition="2015"` is a valid combination.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

The describe functionality can be introduced in several stages:


## First stage: dumb field

At first the `rust` field can be simply a declarative optional field without any
functionality behind it with minimal checks. The reason for it is to reduce
implementation cost of the first stage to the minimum and ideally ship it as part
of Rust 2018. It will also allow crate authors who care about MSRV to start mark
their crates early.

## Second stage: `cargo publish` check

The next step is for `cargo publish` to require use of the toolchain specified
in the `rust` field, for example crates with:
- `rust="stable"` can be published only with a stable toolchain, though not
necessarily with the latest one. Cargo will insert toolchain version before
publishing the crate as was described in the "guide-level explanation".
- `rust="nightly: *"` can be published only with a nightly toolchain. If finer
grained "nightly: ..." (see "nightly versions" section) is selected, then one
of the selected Nightly versions will have to be used.
- `rust="1.30"` can be published only with (stable) Rust 1.30, even if it's
not the latest stable Rust version.

Using the usual build check `cargo publish` will verify that crate indeed can be
built using specified MSRV. This check can be used with exisiting `--no-verify`
option.

## Third stage: versions resolution

`rust` field will be used as a constraint for dependency versions resolution.
If user uses e.g. Rust 1.40 and uses crate `foo = "0.2"`, but
all selected versions of `foo` specify MSRV e.g. equal 1.41 or bigger (or even
nightly) `cargo` will issue an error.
Copy link

@mooman219 mooman219 Jul 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean in this context:

If foo has published versions 0.2.1 (with rust = 1.40) and 0.2.2 (with rust = 1.41), and you're running rust 1.40, depending on foo = 0.2, is the intention to:

  • Error because you explicitly said to use the latest patch of foo, which is 0.2.2, and the latest patch requires a rust version greater than you are running.
  • Build using foo 0.2.1 because it fits your wildcard and works for your rust version?

I would want to make sure your crate fails to build in this case because otherwise you might be using an insecure or error-prone version of a crate in this case even if the issue was patched.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a separate flag to indicate insecure versions, a bump in patch level is meaningless for determining if there are open security issues. Security flaws can be discovered, but not fixed, so reporting error-prone versions should be separate from uploading new versions.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you write foo = 0.2, you're explicitly saying you want the latest patch version of foo 0.2 available, even if that breaks your build.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's explicitly not how it works.

  • 0.2 is compatible with anything in the 0.2.z series
  • If another crate anywhere in the build needs 0.2.1 and latest is 0.2.2, you get 0.2.1 even though it's not latest.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is already precedent for not always pulling the latest, wouldn't rustc needing version X be akin to the create's needs? Not that I need any specific behavior...

I join this thread when I discovered that I couldn't build RLS on Debian's rustc and the failure mode was to try to build and fail. I'm most interested in the cases where rust does build something, but doesn't produce well and good binaries because it's lacking some feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a separate flag to indicate insecure versions

You already can (and probably should) yank insecure crate versions. There is also cargo-audit.

@mooman219
It's "build using foo 0.2.1", the reason for that is already explained. I guess we could emit warnings, so users will be aware about using older versions of crates.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL it doesn't use the latest version if you don't specifically tell it to. My apologies!


`rust` field value will be checked as well, on crate build `cargo` will check if
all upstream dependencies can be built with the specified MSRV. (i.e. it will
check if there is exists solution for given crates and Rust versions constraints)

Yanked crates will be ignored in this process.

Implementing this functionality hopefully will allow to close the debate regarding
MSRV handling in crate versions and will allow crate authors to feel less
restrictive about bumping their crate's MSRV. (though it can be a usefull
convention for post-1.0 crates to bump minor version on MSRV change to allow
publishing backports which fix serious issues using patch version)

## Extension: nightly versions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of flagging supported compiler versions, but I don't quite understand how a crate would actually be able to use this in practice if the list of supported versions has to go in the published Cargo.toml file itself.

Typically when publishing a nightly only crate, I know it works for the current nightly and that it will continue to work for all future nightlies until there is a breaking change. With this design, it seems like I'd need to publish a new version of the crate every day with an updated Cargo.toml saying that the new nightly also works or else my users would be unable to upgrade their compiler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I see it, the main use-case will be to provide exact nightly version, on which crate is developed and tested. So e.g. if you depend on rocket x.y.z which is developed and published with nightly-2019-02-01 toolchain, then your crate will have to be developed with the same toolchain. This will result in a soft synchronization of nightly versions used across nightly crates ecosystem. (e.g. crates will update nightlies ever 2-3 weeks or so, not every night)

If your crate does not experience breakage too often, then you can use default nightly: * option.

Copy link

@fintelia fintelia Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the field to indicate a known good nightly version seems reasonable.

However, I'd be a concerned about it factoring into version resolution then ("stage 3" of the RFC). The entire ecosystem of nightly crates would have to update in lockstep or else become mutually incompatible. Further, trying out new nightlies would require waiting for all your dependencies to be updated. Basically, you'd no longer be running on the nightly channel but instead a sort of "monthly" channel which lacked both the stability of stable and the quick bug fixes of nightly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most of the nightly crates will just use "nightly: *", some may use ranges (though properly testing it will not be easy), finer grained nightly selection will be used only be a handful of crates with rare intersections with each other. Plus do not forget that you always can disable MSRV constraint in dependency versions resolution with --no-msrv-check flag.

Copy link

@fintelia fintelia Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, I'm still not fully understanding. Lets use a concrete example. I maintain the rahashmap crate which depends on some rather unstable features and so breaks every couple weeks. The last time this happened, I fixed the issue and promptly published a new version. This new version supports nightly-2018-07-10, nightly-2018-07-11, and all future nightlies until another breaking change happens. If this RFC & extension were implemented, what would be the correct value for MSRV?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with "all future nightlies until another breaking change happens" is that this constraint can be properly expressed at the moment of crate publishing, as you don't know when breaking change will happen. The closest thing which we can get is to use rust="nightly: >= 2018-07-10" (it's mentioned in the extension). On the next breaking change you'll publish rust="nightly: >= 2018-08-10". So assuming that user keeps all his dependencies updated, with new toolchain he'll use newer crate version, and on old toolchain the older crate version will be used. But nightly: * will not be much worse and will require significantly less effort from you.

But this approach feels quite fragile. To reliably solve this problem we will need an additional channel for notifying users "this crate broke on the following nightly versions" which can't be done via Cargo.toml.


For some bleeding-edge crates which experience frequent breaks on Nightly updates
(e.g. `rocket`) it can be useful to specify exact Nightly version(s) on which
crate can be built. One way to achieve this is by using the following syntax:
- auto-select: "nightly" This variant will behave in the same way as "stable", i.e.
it will take a current nightly version and will use it in a "more or equal" constraint.
- single version: "nightly: 2018-01-01" (tha main variant)
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
- enumeration: "nightly: 2018-01-01, 2018-01-15"
- semver-like conditions: "nightly: >=2018-01-01", "nightly: >=2018-01-01, <=2018-01-15",
"nightly: >=2018-01-01, <=2018-01-15, 2018-01-20". (the latter is interpreted as
"(version >= 2018-01-01 && version <= 2018-01-20) || version == 2018-01-20")

Such restrictions can be quite severe, but hopefully this functionality will be
used only by handful of crates.

## Extension: cfg based MSRV

Some crates can have different MSRVs depending on target architecture or enabled
features. In such cases it can be usefull to describe how MSRV depends on them,
e.g. in the following way:
```toml
[package]
rust = "1.30"

[target.x86_64-pc-windows-gnu]
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
rust = "1.35"

[target.'cfg(feature = "foo")']
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
rust = "1.33"
```

All `rust` values in the `target` sections should be equal or bigger to a `rust` value
specified in the `package` section.

If target condition is true, then `cargo ` will use `rust` value from this section.
If several target section conditions are true, then maximum value will be used.

# Drawbacks
[drawbacks]: #drawbacks

- Declaration of MSRV, even with the checks, does not guarantee that crate
will work correctly on the specified MSRV, only appropriate CI testing can do that.
- More complex dependency versions resolution algorithm.
- MSRV selected by `cargo publish` with `rust = "stable"` can be too
conservative.

# Rationale and Alternatives
[alternatives]: #alternatives

- Automatically calculate MSRV.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems too vague to have much meaning; there are wildly different things it could mean, with completely different trade-offs. What is automatically calculating the MSRV? rustc, cargo, crates.io? How is it conveyed? Would it play well with the future work of MSRV influencing version resolution? I can think of two radically different approaches to this off the top of my head, one of which is completely different from this proposal and one of which boils down to just augmenting the package.rust field of this specification with some tooling to automate discovery of the MSRV.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to cover all potential approaches, as you do say yourself there are a lot of options to choose from. The main idea here is that instead of asking people to manually select MSRV via the rust field, we could rely on some automatic system (be it on rustc, cargo or crates.io side) to partially solve issues which have motivated this RFC.

- Do nothing and rely on [LTS releases](https://github.com/rust-lang/rfcs/pull/2483)
for bumping crate MSRVs.
newpavlov marked this conversation as resolved.
Show resolved Hide resolved

# Prior art
[prior-art]: #prior-art

Previous proposals:
- [RFC 1707](https://github.com/rust-lang/rfcs/pull/1707)
- [RFC 1709](https://github.com/rust-lang/rfcs/pull/1709)
- [RFC 1953](https://github.com/rust-lang/rfcs/pull/1953)
- [RFC 2182](https://github.com/rust-lang/rfcs/pull/2182) (arguably this one got off-track)

# Unresolved questions
[unresolved]: #unresolved-questions

- Name bike-shedding: `rust` vs `rustc` vs `min-rust-version`
- Additional checks?
- Better description of versions resolution algorithm.
- How nightly versions will work with "cfg based MSRV"?