-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Conversation
text/0000-min-rust-version.md
Outdated
If you are sure that your crate supports older Rust versions (e.g. by using CI | ||
testing) you can change `rust` field accordingly. On `cargo publish` it will be | ||
checked that crate indeed can be built with the specified version. (though this | ||
check can be disabled with `--no-verify` option) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the intended meaning of "checked that crate indeed can be built with the specified version", use rustup
to install the specified version and attempt to package with it?
It seems like this could be covered by the error listed above:
In case if you have
rust="stable"
, but executecargo publish
with Nigthly toolcahin you will get an error.
if you have rust = "1.30"
then you will only be allowed to publish when using Rust v1.30.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had in mind that cargo publish
in addition to the current toolchain will build crate with MSRV toolchain with some additional checks.But you are right, if we extend behaviour described for "stable"/"nightly" (i.e. you can cargo publish
only using MSRV toolchain), then describe checks will be redundant. I'll update RFC accordingly.
text/0000-min-rust-version.md
Outdated
error. Although this check can be disabled with `--no-rust-check` option. | ||
|
||
`rust` field should respect the following minimal requirements: | ||
- value should be equal to "stable", "nigthly" or to a version in semver format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this allow a full string, like the date of a nightly build? For some tools which basically is built for a specific nightly (clippy comes to mind) this can be a useful tool. As well as that, it would be useful as AFAIK 1.xx.0-nightly
refers to all of the nightlys that have been built during the six weeks.
Edit: I see this is mentioned later on, but shouldn't it be a Rust version and not just any semver anyways?
Also there is a typo there with the spelling of nightly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see much merit in using 1.xx.0-nightly
versions instead of the simple "nightly". I think it's a reasonable assumption that nightly toolchain will stay fairly up-to-date for most of the developers, and for crates which experience frequent breakage it's better to have finer-grained control provided by nightly: 2018-01-01
approach.
Thank you for noticing the typo, I'll fix it!
text/0000-min-rust-version.md
Outdated
`rust` field should respect the following minimal requirements: | ||
- value should be equal to "stable", "nigthly" or to a version in semver format | ||
("1.50" is a valid value and implies "1.50.0") | ||
- version should not be bigger than the current stable toolchain |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: ..."
.
text/0000-min-rust-version.md
Outdated
rust-cases = [ | ||
{ cfg = "x86_64-pc-windows-gnu", version = "1.35" }, | ||
{ cfg = 'cfg(feature = "foo")', version = "1.33" }, | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer we reuse the existing [target.*]
section for this:
rust = "1.30"
[target.x86_64-pc-windows-gnu]
rust = "1.35"
[target.'cfg(feature = "foo")']
rust = "1.33"
(Note that cargo should either fully support [target.'cfg(feature = "foo")'.dependencies]
or outright reject it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, reusing target
is a good idea. I'll replace rust-case
with your proposal.
As for [target.'cfg(feature = "foo")'.dependencies]
, while it's somewhat redundant to optional = true
dependencies + foo = [".."]
, I think it could provide a better flexibility in specification of optional dependencies (e.g. add dependency only for given target if feature is enabled), so in my opinion it should be supported.
text/0000-min-rust-version.md
Outdated
crate can be built. One way to achieve this is by using the following syntax: | ||
- single version: rust = "nightly: 2018-01-01" | ||
- enumeration: "nightly: 2018-01-01, 2018-01-15" | ||
- (inclusive) range: "nightly: 2018-01-01..2018-01-15" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using ..
as inclusive range in Rust is gonna be confusing. Could we incorporate the semver operators instead?
nightly: >=2018-01-01, <=2018-01-15
or
>=nightly-2018-01-01, <=nightly-2018-01-15
(every new nightly is considered a major version for sure.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not particularly attached to the proposed syntax (I think that exact versions ("nightly: 2018-01-01") will be used much more often than other variants), so probably using semver operators will be indeed a better choice.
Though, after some thought I've started to worry about inconsistency between how "stable" and "nightly" work. I think it will be better to make "nightly" work in the same fashion as "stable", i.e. cargo publish
will select version of the currently used nightly toolchain. "nightly: *" in turn will be used to specify "all nightly Rust versions". cargo init
should probably use the latter variant on nightly. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me, though it makes me think if it makes sense to mix both stable and nightly in the same line?
rust = "1.26, nightly: >=2017-01-01"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, any use-case examples? As I see it, if code enables nightly features it can't work on stable, and if it works on stable it should work on nightly. Well, excluding potential nightly regressions of course, which I think we can safely ignore.
UPD: Also there is a very old nightly versions possibility, but I think we can ignore such cases as well. Though from consistency point of view, such pattern could be allowed, but I am not sure if it's worth the additional complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@newpavlov num-traits
uses a build script to determine whether to enable i128
, which covers both stable and nightly. Though the said build script could emit an error itself meaning there's probably no real use case 🙃.
I think i like the key name be I actually think the channels should not be mixed in. Since it's using semver, I also think that if a crate is nightly only. It can mark itself in an permanently unstable attribute, maybe something like |
Personally I don't have a strong preference regarding the name, so it could be changed on implementation. But if we consider other compilers, then in my opinion Channels are mostly for convenience and providing sensible default option. Using "1.0" as a default value will result in too relaxed constraints on crate versions, thus making the feature significantly less useful in a long run. While the proposed channel handling will result in a too conservative constraint, at least we'll know for certain that crate can be built with a specified version. As for |
@aturon |
It might be nice if there were an env|cli|profile option to have cargo emit an error or warning if the used toolchain is newer than specified for |
This should also help with crates.io have a badge for it too |
text/0000-min-rust-version.md
Outdated
versions from `0.1.0` to `0.1.10`, 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 Rust 1.33 `cargo` will select | ||
`foo v0.1.5`, and `foo v0.1.10` if you'll build your project with Rust 1.30 or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be "foo v0.1.10
if you build your project with Rust 1.40 or later" (1.30 -> 1.40)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are right. Thank you for noticing the typo!
text/0000-min-rust-version.md
Outdated
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` |
There was a problem hiding this comment.
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:
-
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 between1.30
(inclusive) and2
(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 than1.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. -
A user might also declare the MSRV to be
stable
. When published to crates.io,stable
is converted tostable(1.30)
with1.30
being the current stable release. This means that the published artifact will work with a compiler between version1.30
(inclusive) and2
(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 between1.30
(inclusive) and2
(exclusive). But if the crate author opts to publish a new crate version, the MSRV for the new artifact is automatically raised tostable(1.31)
which means that it guarantees to work with a compiler version between1.31
(inclusive) and2
(exclusive). This MSRV change wound not be considered a breaking change since it was already declared before.
Therefore a crate author has to options:
- Fix the MSRV to a specific version, e.g.
1.30
. - 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...)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 be1.29
,1.28.1
, ... in the next crate version.stable(1.30)
can be1.29
,1.28.1
, ... orstable(1.30.1)
,stable(1.31)
, ... or1.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
.)
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
- fixed typo and RFC link - improved wording a bit - added `--check-msrv-toolchain` option ( @phaylon )
text/0000-min-rust-version.md
Outdated
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
Shall it support usual version syntax syntax like |
Even if it was supported, I don't think it would something that would be useful, as it is only marking the absolute minimum version of Rust needed. The support for that syntax makes more sense for crates. |
If you accept the fact that rust has in the past and will in the future make non-backward compatible changes, then a maximum supported version (or in other words a complete dependency range with minimum and maximum) is useful for crate lib authors. See also rust-lang/rust#52320 for implications regarding how this is a two way street of effects. |
@dekellum Take a look at the inline review comments about nightly versioning above. Specifying a maximum supported version in the Cargo.toml of a crate generally isn't helpful: you typically don't know when the next relevant breaking change will happen. And once you've published a crate, if there is later a breaking change, you can't go back and change it to say what the maximum supported version is. |
@fintelia, I respectively still think a maximum supported version is useful and applicable. I/you can publish a patch release (e.g. 1.2.1 for a 1.2.0 crate) with a new maximum version bound, once you determine that a stable or nighly rust has a breaking change that effects the lib. |
The problem is that on "broken" newer stable Rust Either way maximum bounds can be introduced in a separate RFC, after baseline version of this proposal will be implemented. |
Thanks @newpavlov. I agree that maximum bound could be introduced later/incrementally (edit 3, but see below, I think it should be included now.) We know that Edit 1: The 1.2.1 change is not dissimilar to after-the-fact narrowing of a regular crate dependency, (e.g. foodep = "^1.2" later changed to foodep = ">= 1.2.0, < 1.4.0") in a later patch, other than the likehood of Edit 2: To update metadata even without explicit cargo or crates.io support, I just publish an updated 1.2.1 and then yank 1.2.0. |
RFC 2495 (rust-lang/rfcs#2495) was merged quite a while ago, but I think it was never actually implemented.
feat: Add `-Zmsrv-policy` feature flag ### What does this PR try to resolve? Nothing noticeable.... The intent is to unblock experiments with different compatible MSRV policies like - #9930 - #10653 - #10903 While I normally don't like PRs that do nothing on their own, this at least allows any one of those efforts to move forward with different people without juggling these base commits for whoever is first to include in their PR While there isn't an RFC for this yet, this is intended to allow us to experiment to get a better idea of what we should put in an RFC. In some cases, we first do an eRFC for this but I assumed this wouldn't be needed in this case as this builds on rust-lang/rfcs#2495 and, I'm assuming, will be more surgical in nature ### How should we test and review this PR? The `Summary` changes are largely untested as they will be mostly tested through the future work that builds on this PR. However, I wasn't too concerned about that because the code is relatively trivial. ### Additional information I chose the name `msrv-policy` to distinguish this unstable feature from `rust-version`. Though those appear in different places (`Cargo.toml` vs `-Z`), I can see them being confusing which was especially apparent when editing `unstable.md` which has an anchor for `rust-version`.
This RFC adds an ability to specify Minimum Supported Rust Version (MSRV) in
Cargo.toml
usingrust
field:Rendered
Tracking issue
Internals discussion
Edit: FCP proposal #2495 (comment)