-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Stabilize #[cfg(version(...))]
, take 2
#141766
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
base: master
Are you sure you want to change the base?
Conversation
#[cfg(version(...))]
, take 2
I'll highlight for our awareness that this test passes: //@ run-pass
//@ rustc-env:RUSTC_OVERRIDE_VERSION_STRING=1.86.0
#![feature(cfg_version)]
fn main() {
assert!(cfg!(not(version("1.85.65536"))));
} That is, we're exposing that "1.85.65536 > 1.86.0". I don't really love that, in terms of specifying the language, but I understand the motivation for it. Probably we should make sure to say in the Reference that the behavior when the version string does not conform to the current requirements is unspecified and may change in the future. |
This all still looks right to me. Inclusive of taking the normative position that the behavior of The best day to add @rfcbot fcp merge |
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
What's the reason for this? Is it just an accident of implementation, or is there a use case for it? |
If we change the versioning scheme in the future -- we start naming our versions "25-06" or something -- then you'd want older versions of Rust to just accept those and assume they must be from the future. So it treats parse errors as "must be from the future". It's parsing each segment into a |
yeah, if you do
It's possible to use bignums to fix this but I'd say it's a non-issue: even u16 gives a millenium worth of releases (at the current cadence). And we can always extend it in the future should we anticipate an increase in velocity. |
Seems like something that would deserve at least a warn-by-default lint, if not deny-by-default? (Although that's not a blocker for stabilization, the lint can always be added later.) |
Rather than bignum, what I had nearly proposed (before deciding I was OK with how it is) was to saturate anything matching |
It does warn if the version string literal does not parse. |
Yeah, it's a builtin warning, without a way to opt out. Which is not convenient to deal with if you face it, but this also serves as a good deterrent, better than a warn-by-default lint. IF a new versioning scheme is being introduced, hopefully most of the population will be on newer compilers that either recognize the versioning scheme, or it has been turned into a lint at that point. That at least was my original reasoning why I made it into a warning and not a lint. In any case, warning or lint, we don't lock ourselves in in any way due to this stabilization, even if we turn it into an error-by-default lint let's say. |
@rfcbot reviewed |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@rfcbot reviewed |
For what it's worth there was discussion in rust-lang/rfcs#3796 to permit |
@epage The only way
Regarding
|
Except if we release |
Perhaps a silly question, but what is the use case for wanting to gate something on a point release? |
@rfcbot concern let's take rust_version seriously Having given this some thought, I think While I agree that the Previously I had wanted to use something like #[cfg(has_cfg_version)]
#[cfg(version("1.80"))]
fn foo() {}
#[cfg(not(has_cfg_version))]
fn foo() {}
#[cfg(has_cfg_version)]
#[cfg(not(version("1.80")))]
fn foo() {} That's actually quite important, and something I had missed in our earlier discussion on this. It means that there isn't, in my view, a satisfactory way to retrofit something like So as much as I would like to get this feature today, I don't think we should rush it out the door. As this discussion and previous discussions have shown, there have been more design concerns hiding underneath the question of whether we can ship I'm also happy to put time in to writing an RFC, FCP, or whatever's needed and drive consensus on it – and having raised a concern, driving this will be a priority for me. On that note, a huge thanks to @est31 for being willing to roll with the punches as we work out the issues here. |
There is no reason Rust 1.86 wouldn't know about it, when we would do a stable-point release we would backport that knowledge to the current stable/beta/nightly releases, technically This also assumes that we do want to distingues about the point releases, we could also simply have: |
One of the points that's been made in this thread that stands out strongly to me is the potentially limited window of utility for not giving an error in earlier versions. If you're maintaining an MSRV of 1.48 while also using If doing something like this were a big help to people with 1.48 MSRVs today, then that would be one thing. But I sense that it's probably not. In any event, there is, I think, more work for us to eventually do here. E.g., I'd like to see us add a |
I think it's a solid point @traviscross. I haven't done a comprehensive analysis, but in the two crates I looked at we might gain only a year; perhaps less, if we take too long. There are also a couple of things that push me in the other direction:
Certainly there is still time pressure to maximize the value of the thing we ship. I just don't want that to be the overriding theme when there are legitimate design concerns, especially if they can (arguably) have a similarly sized impact. |
Thanks @traviscross for putting it in better words than what I wrote in #141766 (comment) I'd say the major advantage of So |
For me, my overriding sensation isn't time pressure to ship, but simple humility. The tradeoffs we're discussing today were also largely discussed at the time of RFC acceptance and would have been reviewed at the time of the original stabilization attempt. Many good and reasonable points were raised, and the team seems to have acted reasonably in making adjustments to take those into account, leading to what's before us today and what would likely have been stabilized in Rust 1.53 had it not been for the concern about stabilizing It's just not yet clear to me, in this case, that much has changed or that we're certain to do better. It seems within the realm of the possible that we go through another round and come back with the same design. Conversely, the cost of shipping this design today and making any later extensions or adjustments, if we were to decide we wanted them, seems low. My humility extends as well to our team bandwidth. We have a lot of important work stacked up. Perhaps I just don't have that much appetite for putting this one back into our queue. |
If you were to adopt a syntax using |
Cargo already uses |
eh, that's okay I guess. having drafted a PR using it, I guess
However, I do not understand the current implementation treating nightly versions as "complete". I understand dtolnay's opinion re: nightly versions being treated as complete as "providing bad DX", but I believe that flags like the one currently implemented in rustc... the ability to "opt-out" of the completeness assumption... should have their polarity run the other way. If you want to opt in to a feature immediately, as in the case described, you are more motivated to learn how to make that work. We can even have Far more people are going to compile code instead of write it, so telling every distro and every dependent to add this flag whenever they run the nightly compiler seems strictly worse than guiding upstreams... the ones interactively using the compiler repeatedly... to the thing they want. I also think library authors would rather And yes, I understand the opinion regarding version pinning needing to be propagated, I have said as much myself, but I will spare everyone the much longer dissertation on how that simply doesn't fully add up and unnecessarily removes degrees of freedom. |
How nightly compilers interpret this syntax is not, I'd suggest, anything to which we necessarily need to commit in this stabilization. We could always change that without breaking any stability guarantees. |
I'm generally sympathetic, and I am quite happy applying this argument to many of the questions settled in the RFC. It considered quite a large design space, including things like whether we should stabilize feature flags instead of using versions. Even if we want those things, I think we should do them after the functionality we already agreed to. I don't think the specific syntax was ever settled though. It's true that this issue was raised on the original RFC – by the maintainer of the libc crate, 3 days before FCP ended – and the result is actually that the RFC author said we should reconsider the syntax ahead of stabilization. We also see an unresolved question about the There is a big picture argument that I think also applies. This RFC was written in 2018; since then, Rust's success and its proliferation in places like Linux distros has made maintaining an MSRV much more common than in extreme cases like libc. Rust has also shipped a lot of useful features. So I think some of the qualitative tradeoffs have shifted in that time. Of course, if we had stabilized this feature at exactly the time the RFC was accepted it wouldn't matter, but it does now. Note: The point about the unresolved syntax question makes me wonder whether there should be a new RFC to consider the |
FWIW, while I think there are several reasons not to switch the syntax, I don't think "momentum" should be one of them. If we actually think there's a better alternative, we should consider it. I think the important question is whether the alternative is actually better. Also, orthogonally to the question of whether we should use @m-ou-se wrote:
I agree with this. @m-ou-se wrote:
I wouldn't expect to see that. I'd expect a project to wait until their MSRV allows
I see. You're arguing that, as long as current versions emit a warning saying that That seems like we'd be deliberately choosing more error-prone behavior, in order to gain partial backwards compatibility for the sole benefit of programs that want to detect newer rust versions than their MSRV but don't need to detect rust versions between their MSRV and when we ship this version detection mechanism. Any program that wants to have behavior for e.g. "1.62 or newer" or "older than 1.62" would still need a build script.
I will observe that, in other discussions, we've been talking about moving in the other direction, and writing jhpratt wrote the same thing here: #141766 (comment) @est31 wrote:
This is exactly what I'm getting at. The @traviscross wrote:
This is exactly my expectation. A crate (and the crates that depend on it) don't get a substantive benefit until the To be explicitly clear, if everyone else prefers the |
Some thoughts. One, it influences me here that there's an existing way to do this (with This isn't a case where, in focusing on what's best for the language going forward, we'd be leaving people in a lurch. People will continue to do what they've been doing successfully, and it will work just as well as it always has. Two, the Three, my reaction to the We haven't yet used The main upside I see to something like The one place I could maybe see using |
@traviscross Fair enough. For my part, I'd happily sign off on either |
There are likely low-MSRV crates that would use version gating but for the fact that it requires adding a build script. A good example of this is If we stabilize For the crates already using build scripts, eventually the old features will move outside the MSRV window. Then the crate author will be able to remove their build script altogether.
Interesting! My thought is that adding a flag in one place is easier than adding it in three (two of them in build.rs: One for the detection and one for check-cfg), and keeping it out of the build script makes it easier to see the reasons why you're still maintaining a build script. But other crate authors might prefer to keep everything as consistent as possible.
This is exactly what I was thinking. The Rust survey shows that >90% of development is on current stable, so I think we will mitigate any misuse quite well with that warning.
In practice, I don't think it's more error prone than other uses of Saying that we shouldn't do it because those versions don't implement a lint strikes me as an unfair comparison. This is a feature whose purpose is to allow compatibility with older compiler versions. On older versions of the compiler we don't have check-cfg or updated lints that catch other kinds of misuse, because of course we can't go back and add them. So the choice is between whether we want something that works at all on those older versions, or whether we want something that doesn't work but is impossible to misuse. When I think of it that way, and include the fact that we can make misuse very unlikely with a lint, the choice seems clear. |
I think of it this way: The pressing motivation to stabilize this is to allow more crates to move off of build scripts as soon as possible so that we can improve build times. The most effective way to do that is to choose a syntax that is compatible with old MSRVs, so that we don't force the use of build scripts on old-MSRV crates even when there are no version gates past the stabilization of this cfg.
Me too. I'd be happy to consider it.
We can support
I can see where you're coming from, but I think our hand might be forced already. Personally, if I consider those two names in isolation, I would rate them about the same. The thing that pushes me in the direction of I have trouble seeing how picking Part of this is that I don't think it's better in a practical sense. The justification in the RFC thread you linked discussed a fork. Putting aside whether we want to plan for that, a fork could rename |
I'd pick
It appears in The situation for something in the language itself is different, for me, and I don't find myself compelled to mirror what they've done, just as we've departed in the language from choices of our other tools in other cases. Our situation, in the language, is just different. |
Cargo also uses So I'd anticipate that we're going to break from Cargo here in one place or the other. |
In the case of Cargo, an unqualified |
That's not the only thing I mean by "error-prone", here. By way of example, They can't necessarily suppress it with #[allow(unexpected_cfgs)] // This doesn't work, the warning still appears
#[cfg(rust_version = "...")]
fn item() { ... } (And even if that did work, it would also suppress the warning inside the item.) What does work is So, in a variety of ways, using I also find it quite motivating to observe that, in a few years, the versions that don't support |
I don't think this is right, are you sure? The two mechanisms were announced together: https://blog.rust-lang.org/2024/05/06/check-cfg/, and this was after going through compiler and cargo FCPs at around the same time. (Actually, looks like the blog post was edited to include the Cargo piece two weeks after it posted, but that's still within a release cycle.)
I would expect a user to have an MSRV-testing job; I would not expect them to use I suppose if you want to be conservative you might look at warnings that only occur in your MSRV just in case they matter in only that version. I still don't think My point: Whatever the ground truth is of how old versions of the compiler worked, we can't change it, but users can make a range of reasonable policy decisions in their CI based on it. |
Stabilization report
This proposes the stabilization of
cfg_version
(tracking issue, RFC 2523).What is being stabilized
Permit users to
cfg
gate code sections based on the currently present rust version.Tests
cfg-version-expand.rs: a functional test that makes rustc pretend to be
1.50.3
, then tries with1.50.0
,1.50.3
, and1.50.4
, as well as other version numbers.syntax.rs: tries various ways to pass wrong syntax to
cfg(version)
:#[cfg(version("1.20.0"))]
#[cfg(version("1.20"))]
are allowed, but#[cfg(version("1"))]
is not#[cfg(version("1.20.0-stable"))]
are not allowed#[cfg(version = "1.20.0")]
is not supported, and there is a warning of theunexpected_cfgs
lint (but no compilation error)assume-incomplete.rs: another functional test, that uses macros. It also tests the
-Z assume-incomplete-release
flag added by #81468.wrong-version-syntax.rs ensures that check_cfg gives a nice suggestion for
#[cfg(version("1.2.3"))]
when someone tries to do#[cfg(version = "abc")]
.Development of the implementation
The initial implementation was added by PR #71314 which used the
version_check
crate.PR #72001 made
cfg(version(1.20))
eval to false onnightly
builds with version1.20.0
, upon request from the lang team. This decision was pushed back on bydtolnay
in this comment, leading tonikomatsakis
reversing his decision.Ultimately, a compromise was agreed upon, in which nightly releases are treated as "complete" i.e.
cfg(version(1.20))
evals to true onnightly
builds with version1.20.0
, but there is a nightly flag-Z assume-incomplete-release
to opt into the behaviour that doesn't do this assumption. This compromise was implemented in PR #81468.PR #81259 made us adopt our own parsing code instead of using the
version_check
crate.PR #141552 pulled out the syntactic checks from the feature gate test into its own dedicated test.
PR #141413 made
#[cfg(version)]
more testable by making it respectRUSTC_OVERRIDE_VERSION_STRING
.Prior stabilization attempts were #64796 (comment) and #141137.
cfg_has_version
In the course of the earlier stabilization attempt, it came up that due to the way
#[cfg(version)]
uses "new" syntax, one can only adopt it if the MSRV includes the version that stabilized#[cfg(version)]
. So it won't be immediately useful: For a long time, many crates will still use the alternatives that#[cfg(version)]
meant to displace, until the stabilization of#[cfg(version)]
was sufficiently long ago.In order to solve this,
cfg_has_version
was proposed: a builtin, always true cfg variable. Ultimately, the lang team decided in #141401 to not immediately includecfg_has_version
into the stabilization (#141137 included it), but go via a proper RFC instead. Implementation wise,cfg_has_version
is not hard to implement, but semantically, a cfg variable is not a small deal, it will be present everywhere, e.g. inrustc --print cfg
.There is no such thing as unstable cfg variables (and even if there were, it would counteract the purpose of
cfg_has_version
), so its addition would have an immediate-stable effect.In a couple of months to a couple of years, this will not be a problem, as the MSRV of even slower moving projects like serde gets bumped every now and then. We probably feel the desire for
cfg_has_version
the strongest directly after the stabilization of#[cfg(version)]
, then it decreases monotonically.Unresolved questions
Should we lint for
cfg(version)
probing for a compiler version below the specified MSRV? Part of a larger discussion on MSRV specific behaviour in the Rust compiler. It feels like it should be a rustc lint though instead of a clippy lint.Future work
The stabilization doesn't close the tracking issue, as the
#[cfg(accessible(...))]
part of the work is still not stabilized, currently requiring an implementation (if an implementation is something we'd want to merge in the first place).We also explicitly opt to treat
cfg_has_version
separately.TODOs before stabilization
cfg_version
cargo#15531 -> decided not to do it