-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Version-typed cfgs #3905
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?
Version-typed cfgs #3905
Conversation
weiznich
left a comment
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.
Overall I linke the idea, but I have some questions and remarks
(Obvious disclaimer: I'm not associated with any team, that's just my personal opinion, so feel free to ignore that)
| #[cfg(rust_edition >= "2021")] | ||
| fn my_function() { | ||
| // use a feature only available from the 2021 edition onwards | ||
| } | ||
| ``` | ||
|
|
||
| Note that because new compilers can still compile older editions, the `#[cfg(rust_edition)]` stacking pattern is less useful than it is for `rust_version`. The primary use case for rust_edition is within macros or code generation that needs to produce different code depending on the edition context it's being expanded into. |
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 would be interested in seeing an actual example where this would have been useful. As far as I know macros use the same edition of the crate defining the macro, not the edition of the calling crate.
(Well beside emitting errors that a crate/generated code doesn't support edition 2024 or something like that, which might be problematic for the ecosystem overall)
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.
Code generation is the motivating case. In shipping the 2024 edition, we tracked various problems related to this such as:
(While we shipped unsafe extern in all editions, the generator could maintain a lower MSRV on its output if that output included #[cfg(rust_edition))] in some cases. The generator could support, of course, an --edition flag to conditionalize its output rather than conditionalizing in the output, but that asks more of users, and in general it's convenient when the same generated code can be accepted across editions.)
We also tracked certain macro-related problems such as:
- Fix Rust edition 2024 compatibility wasm-bindgen/wasm-bindgen#4259
- [edition 2024]
missing_unsafe_on_externin macros with older editions rust#132425 - rust_2024_incompatible_pat bad suggestion with proc-macro when brace comes from input rust#132963
- Bad suggestion for macro_rules macros generated from a proc-macro rust#132906
- Use edition of
macro_ruleswhen compiling the macro rust#133274 - regression: a value of type
HashMap<Pulse, u64>cannot be built from rust#135669 - Nested macro_rules and edition spans rust#137031
- matches! problem with edition 2021 rust#84429
- document and test the precise span that triggers edition-dependent behavior rust#86539
With macros that define macros, edition hygiene does not actually work in the way that we might ideally like. Maybe or maybe not #[cfg(rust_edition))] could have helped in averting some problems. Even if not, it doesn't bother me; it's the code generation case I find motivating.
RFCs are the place for community feedback. Insight from future feature users that aren't team members is just as (if not more) valuable, and always welcome in all discussion areas ❤️ |
text/0000-typed-cfgs.md
Outdated
| @@ -0,0 +1,265 @@ | |||
| - Feature Name: typed_cfgs | |||
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.
Sort of a meta comment: this RFC is called typed_cfgs Unless I overlooked something, however, it only talks about version-typed configs, and that is of course the main content of the RFC. Maybe "Version-typed cfgs" would be a more fitting name?
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.
Title aside, if accepting this RFC means accepting the concept of "typed cfgs", I think something about the mental model here is worth introducing in its own section. E.g. is existing config syntax "untyped"? Or is this proposing that cfg(all(foo, bar = "baz", version >= "1.2.3")) can be thought of as something like:
struct Version(/* ... */);
impl PartialOrd<&str> for Version { /* ... */ }
fn cfg_predicate(foo: bool, bar: Set, version: Version, ...) -> bool {
[foo, bar.contains("baz"), version >= "1.2.3"].iter().all()
}For some prior art, C says that everything in an #if declaration gets intmax_t type and the expression must evaluate to an integer, which is then compared to 0.
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.
Hah, I was just typing up a question about this myself, only to realize this is asking basically the same thing. The Effectful Target Tracking experiment wants to bring cfg-predicates into the type system as effect types. If we can spell out how typed cfgs could (at least conceptually) be lowered to types, that would be what we need to figure out how to bridge the two.
I believe @traviscross asked something similar in a previous lang team meeting, about potentially making cfg-features resolvable by const-eval/comptime functions. I believe to explore that too, having a (conceptual) type system representation would be helpful as well.
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.
TC said:
If we instead had typed cfg values we could...
What I'd really like eventually is to not have a DSL here at all and just use Rust within cfg attributes. We already have a Rust interpreter on hand -- consteval. Obviously nothing from the crate itself would be in scope.
Clearly I don't think
cfg_versionshould wait for that. But when we start talking about typed cfg values, generic predicates, and, presumably therefore, type inference and type checking for cfg, it does make me wonder whether using Rust here and consteval might not be more straightforward after all.
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.
Or is this proposing that
cfg(all(foo, bar = "baz", version >= "1.2.3"))can be thought of as something like:struct Version(/* ... */); impl PartialOrd<&str> for Version { /* ... */ } fn cfg_predicate(foo: bool, bar: Set, version: Version, ...) -> bool { [foo, bar.contains("baz"), version >= "1.2.3"].iter().all() }
I'd rather prefer, e.g., if we could write something like:
#[cfg_eval(OPTS.contains("foo") && OPTS.get_eq("bar", "baz") && version >= "1.2.3")]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.
You're right about the title. I renamed the RFC in b5e9e5c.
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.
My mental model is that existing cfgs are a set of strings-or-none, like you suggest with the code sample. I'm not sure where this would go in the RFC.
| # Future possibilities | ||
| [future-possibilities]: #future-possibilities |
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.
Having clippy take cfg rust_version comparisons into account? See https://github.com/rust-lang/rfcs/blob/4551bbd827eb84fc6673ac0204506321274ea839/text/3857-cfg-version.md#clippy
| - Increased compiler complexity. This introduces a new concept of "typed" `cfg`s into the compiler, which adds complexity to the parsing and evaluation logic for conditional compilation. | ||
| - Subtlety of MSRV-preserving patterns: The need for the "stacked `cfg`" pattern (`#[cfg(rust_version)] #[cfg(rust_version >= ...)]` and `#[cfg_attr(rust_version, cfg(rust_version >= ...))]`) is subtle. While we will add lints to guide users, it's less direct than a simple predicate. However, this subtlety is the explicit tradeoff made to achieve MSRV compatibility. | ||
| - The "stacked `cfg`" pattern does not work inside Cargo, so users will not be able to use this feature in Cargo until their MSRV is bumped. For cases where a dependency needs to be conditional on the Rust version, one can define a "polyfill" crate and make use of the MSRV-aware feature resolver, like the `is_terminal_polyfill` crate does. | ||
| - Conditional compilation adds testing complexity. In practice, most crate maintainers only test their MSRV and the latest 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.
The impact can be reduced by tools like cargo hack that let you test across ranges for rust versions
| This approach delivers the most critical functionality to users quickly, while allowing more time to finalize the design for user-defined version predicates. | ||
| # Drawbacks |
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.
From #3857
Libraries could having ticking time bombs that accidentally break or have undesired behavior for some future Rust version that can't be found until we hit that version.
| This approach delivers the most critical functionality to users quickly, while allowing more time to finalize the design for user-defined version predicates. | ||
| # Drawbacks |
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.
Handling pre-releases in the future with a general syntax is fraught with issues, see https://github.com/rust-lang/rfcs/blob/4551bbd827eb84fc6673ac0204506321274ea839/text/3857-cfg-version.md#pre-releases-for-major-versions
| - How should pre-release identifiers in version strings be handled? This RFC proposes not supporting pre-release identifiers in version strings passed on the command line for now. For comparisons, this RFC proposes that if a pre-release identifier is present in a `cfg` predicate (e.g., `rust_version < "2.0-alpha"`), the pre-release part is ignored for the comparison (so it's treated as `2.0`), and a lint is emitted. This ensures forward compatibility, as comparisons like `cfg(all(foo >= "2.0-alpha", foo < "2.0"))` become trivially false on older compilers, which is a safe outcome. This behavior can be refined before stabilization. | ||
| - Should the builtin `rust_version` and `rust_edition` be printed with `--print cfg` on the command line? We'd like the eventual answer to be "yes", but existing tools that parse the output might break with the new `rust_version=version("1.99")` syntax. If we can manage the breakage we should; otherwise we can gate it on a future edition. | ||
|
|
||
| # Future possibilities |
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.
Clippy lint for using this with cfg_alias so people centralize knowledge of the version and have named identifiers to work from. See also https://github.com/rust-lang/rfcs/blob/4551bbd827eb84fc6673ac0204506321274ea839/text/3857-cfg-version.md#clippy-lint-prefer-using-cfgversion_since-via-cfg_alias
text/0000-version-typed-cfgs.md
Outdated
| There is a single, unified parsing and comparison logic that is part of the language's semantics. Additional checks for the built-in version keys are implemented as lints. | ||
|
|
||
| * The comparison is performed component-by-component, filling in any missing components with `0`. For example, a predicate `my_cfg >= "1.5"` will evaluate to true for versions `1.5.0`, `1.6.0`, and `2.0`, but false for `1.4.9`. | ||
| * For `rust_version`, a lint will be issued if the literal has more than two components (e.g., `"1.92.0"`). This is because language features should not depend on patch releases. |
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.
This is because language features should not depend on patch releases.
They shouldn't, but they do sometimes. We've reverted things in patch releases, causing behaviors to flip. It seems reasonable to me that a (particularly careful) library author might reasonably use this mechanism to handle such errors on our part.
text/0000-version-typed-cfgs.md
Outdated
| * The comparison is performed component-by-component, filling in any missing components with `0`. For example, a predicate `my_cfg >= "1.5"` will evaluate to true for versions `1.5.0`, `1.6.0`, and `2.0`, but false for `1.4.9`. | ||
| * For `rust_version`, a lint will be issued if the literal has more than two components (e.g., `"1.92.0"`). This is because language features should not depend on patch releases. | ||
| * A new lint, `useless_version_constraint`, warns for version checks that are logically guaranteed to be true or false (e.g., `rust_version >= "1.20"` when the feature was stabilized in 1.90). | ||
| * For `rust_edition`, a lint will be issued if the literal has more than one component or if we know the value is never going to be a Rust edition (for example, `"2019"`). |
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.
For
rust_edition, a lint will be issued if the literal has more than one component...
Speaking with an edition hat on, we do not guarantee that we might not in the future ship an edition with a dot, e.g. "2028.06". If we were to lint this, it would mean that someone on an older toolchain compiling valid code produced by a generator handling this new edition would get the warning. Maybe that's OK. Maybe generators would suppress this diagnostic anyway. It's something to think about.
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.
My understanding was edition names were kept open ended originally but we have little reason to do anything else at this point. This came up because there was a proposal to change package.edition to an integer.
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 haven't gotten through all the latest comments but will continue going through them tomorrow.
text/0000-version-typed-cfgs.md
Outdated
| The primary blockers for existing solutions have been: | ||
|
|
||
| - **Build Scripts are a Poor Solution:** The only stable tool for this today is a build script (`build.rs`). However, build scripts add significant compilation overhead and are clunky to write and maintain. | ||
| - **Previous Attempts had Flaws:** Past RFCs have tried to solve this, but ran into an unfortunate issue: their proposed syntax, e.g. `#[cfg(version(1.85))]`, was a syntax error on older compilers. This means that to use the feature, a library would first have to bump its MSRV to the version that introduced the syntax, somewhat defeating the primary purpose of the feature. |
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.
My intent is not to connotate something irredeemable. I don't think "flaws" does that, but I am open to suggestions on wording.
I agree we are in the realm of engineering tradeoffs, but I would add some additional context, e.g. the lack of an established decision or consensus on the existing syntax/naming and the chance to solve new problems we discovered in the meantime.
text/0000-version-typed-cfgs.md
Outdated
| * For `rust_version`, a lint will be issued if the literal has more than two components (e.g., `"1.92.0"`). This is because language features should not depend on patch releases. | ||
| * A new lint, `useless_version_constraint`, warns for version checks that are logically guaranteed to be true or false (e.g., `rust_version >= "1.20"` when the feature was stabilized in 1.90). | ||
| * For `rust_edition`, a lint will be issued if the literal has more than one component or if we know the value is never going to be a Rust edition (for example, `"2019"`). |
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 leaving that feature of --check-cfg as a future possibility, which I describe below. I agree it would be nice and might consider including it in the RFC. On the other hand we can add it later, and we might benefit from seeing how it gets used in the ecosystem.
text/0000-version-typed-cfgs.md
Outdated
| * For `rust_version`, a lint will be issued if the literal has more than two components (e.g., `"1.92.0"`). This is because language features should not depend on patch releases. | ||
| * A new lint, `useless_version_constraint`, warns for version checks that are logically guaranteed to be true or false (e.g., `rust_version >= "1.20"` when the feature was stabilized in 1.90). | ||
| * For `rust_edition`, a lint will be issued if the literal has more than one component or if we know the value is never going to be a Rust edition (for example, `"2019"`). | ||
| * Pre-release identifiers (e.g., `"1.92-beta"`) are not supported in this RFC. They will be ignored during comparison and a lint will be emitted. See the "Unresolved Questions" section for further discussion. |
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.
We should specify the set of accepted prereleases upfront. I'll add that to the RFC.
text/0000-version-typed-cfgs.md
Outdated
| The primary blockers for existing solutions have been: | ||
|
|
||
| - **Build Scripts are a Poor Solution:** The only stable tool for this today is a build script (`build.rs`). However, build scripts add significant compilation overhead and are clunky to write and maintain. | ||
| - **Previous Attempts had Flaws:** Past RFCs have tried to solve this, but ran into an unfortunate issue: their proposed syntax, e.g. `#[cfg(version(1.85))]`, was a syntax error on older compilers. This means that to use the feature, a library would first have to bump its MSRV to the version that introduced the syntax, somewhat defeating the primary purpose of the feature. |
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.
Also, this bullet is really about RFC 2523 specifically. I'll add wording to clarify that.
text/0000-version-typed-cfgs.md
Outdated
| --check-cfg 'cfg(my_app_version, version())' | ||
| ``` | ||
|
|
||
| This will accept any version value, but lint when the option is used as something other than a version. This is a more sensible default for versions, which don't have the equivalent of `values(none())`. |
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.
It was unclear; improved the wording in 2da8ddb.
On the default, the only other possibility I see is to lint on every version comparison with that option. I'm having a hard time seeing how that's useful or desirable.
text/0000-version-typed-cfgs.md
Outdated
| The primary blockers for existing solutions have been: | ||
|
|
||
| - **Build Scripts are a Poor Solution:** The only stable tool for this today is a build script (`build.rs`). However, build scripts add significant compilation overhead and are clunky to write and maintain. | ||
| - **Previous Attempts had Flaws:** Past RFCs have tried to solve this, but ran into an unfortunate issue: their proposed syntax, e.g. `#[cfg(version(1.85))]`, was a syntax error on older compilers. This means that to use the feature, a library would first have to bump its MSRV to the version that introduced the syntax, somewhat defeating the primary purpose of the feature. |
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.
Improved this section in 2323793.
| ```text | ||
| version_literal : | ||
| NUMERIC_COMPONENT ('.' NUMERIC_COMPONENT)* | ||
|
|
||
| NUMERIC_COMPONENT : | ||
| '0' | ||
| | ('1'...'9') ('0'...'9')* |
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 find the use of the word "component" to be confusing, for instance I had missed that the future possibility about components was even referring to this.
semver.org refers to them as version identifiers. I tend to refer to them as "fields" within the version.
| - **More expressive check-cfg:** We can support specifying an expected number of components in check-cfg, or an expected set of values to compare against, as in editions: | ||
| - `--check-cfg 'cfg(foo, version("2018", "2022", "2025"))'` | ||
| - `--check-cfg 'cfg(foo, version(values >= "1.75"))'` | ||
| - `--check-cfg 'cfg(foo, version(components <= 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.
Combining the description with the example makes it easier to see what the intent is
| - **More expressive check-cfg:** We can support specifying an expected number of components in check-cfg, or an expected set of values to compare against, as in editions: | |
| - `--check-cfg 'cfg(foo, version("2018", "2022", "2025"))'` | |
| - `--check-cfg 'cfg(foo, version(values >= "1.75"))'` | |
| - `--check-cfg 'cfg(foo, version(components <= 2))'` | |
| - **More expressive check-cfg:**, including | |
| - supported versions (discrete) `--check-cfg 'cfg(foo, version("2018", "2022", "2025"))'` | |
| - supported versions (range) `--check-cfg 'cfg(foo, version(values >= "1.75"))'` | |
| - max supported precision within a version`--check-cfg 'cfg(foo, version(components <= 2))'` |
| * For `rust_version`, a lint will be issued if the literal has more than two components (e.g., `"1.92.0"`). This is because language features should not depend on patch releases. | ||
| * A new lint, `useless_version_constraint`, warns for version checks that are logically guaranteed to be true or false (e.g., `rust_version >= "1.20"` when the feature was stabilized in 1.90). | ||
| * For `rust_edition`, a lint will be issued if the literal has more than one component or if we know the value is never going to be a Rust edition (for example, `"2019"`). | ||
| * Pre-release identifiers (e.g., `"1.92-beta"`) are ignored during comparison and a lint will be emitted. The comparison acts as if the pre-release was not specified. See the "Unresolved Questions" section for further discussion. |
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.
Another breaking change from this:
< "1.75.0" with 1.75.0-dev.1:
- With this RFC, evaluates to
truebecause the pre-release is ignored - When pre-releases are supported, would evaluate to
falsebecause all pre-releases are less than non-pre-releases.
text/0000-version-typed-cfgs.md
Outdated
|
|
||
| * The comparison is performed component-by-component, filling in any missing components with `0`. For example, a predicate `my_cfg >= "1.5"` will evaluate to true for versions `1.5.0`, `1.6.0`, and `2.0`, but false for `1.4.9`. | ||
| * For `rust_version`, a lint will be issued if the literal has more than two components (e.g., `"1.92.0"`). This is because language features should not depend on patch releases. | ||
| * A new lint, `useless_version_constraint`, warns for version checks that are logically guaranteed to be true or false (e.g., `rust_version >= "1.20"` when the feature was stabilized in 1.90). |
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.
Why is this indented? This is not further information about a number of components lint but describing a completely different lint, making it harder to notice (case in point: #3905 (comment))
text/0000-version-typed-cfgs.md
Outdated
|
|
||
| * **`--print check-cfg`**: The built-in `rust_version` and `rust_edition` cfgs are implicitly included, so `rustc --print=check-cfg` will always list them. We can add these immediately because `--print check-cfg` is unstable. | ||
|
|
||
| * **Clippy**: Clippy's `incompatible_msrv` lint should be updated to respect `rust_version` checks, avoiding false positives when code is guarded by a sufficient `rust_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.
This seems unrelated to the section it is under:
The
versiontype integrates with existing compiler flags.
| This will accept any version value, but lint when the option is used in a non-version comparison (note that this is an error if the option actually has a version-typed value). This is a more sensible default for versions, which don't have the equivalent of `values(none())`. | ||
|
|
||
| * **`--print cfg`**: User-defined version cfgs are printed in the `name=version("...")` format. Whether to print the built-in `rust_version` and `rust_edition` cfgs is left as an unresolved question to be determined based on tool compatibility. In future editions, the builtin cfgs should always be printed. | ||
| * Note: Using editions being careful about passing `--edition` to `rustc --print cfg` invocations, which `cargo` for example does not currently do. This could introduce unexpected inconsistencies. |
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 feel like this could easily be lost when discussing the unresolved question around this. Should this be moved under the unresolved question?
| - How should pre-release identifiers in version strings be handled? This RFC proposes not supporting pre-release identifiers in version strings passed on the command line for now. For comparisons, this RFC proposes that if a pre-release identifier is present in a `cfg` predicate (e.g., `rust_version < "2.0-alpha"`), the pre-release part is ignored for the comparison (so it's treated as `2.0`), and a lint is emitted. This ensures forward compatibility, as comparisons like `cfg(all(foo >= "2.0-alpha", foo < "2.0"))` become trivially false on older compilers, which is a safe outcome. This behavior can be refined before stabilization. | ||
| - Should the builtin `rust_version` and `rust_edition` be printed with `--print cfg` on the command line? We'd like the eventual answer to be "yes", but existing tools that parse the output might break with the new `rust_version=version("1.99")` syntax. If we can manage the breakage we should; otherwise we can gate it on a future edition. |
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.
Are these intentionally be left to be resolved during stabilization or are these meant to be resolved before the RFC is approved?
…nethercote Make `--print=check-cfg` output compatible `--check-cfg` arguments This PR changes significantly the output of the unstable `--print=check-cfg` option. Specifically it removes the ad-hoc resemblance with `--print=cfg` in order to output a simplified but still compatible `--check-cfg` arguments. The goal is to future proof the output of `--print=check-cfg` like `--check-cfg` is, and the best way to do that is to use it's syntax. This is particularly relevant for [RFC3905](rust-lang/rfcs#3905) which wants to introduce a new predicate: `version(...)`.
…nethercote Make `--print=check-cfg` output compatible `--check-cfg` arguments This PR changes significantly the output of the unstable `--print=check-cfg` option. Specifically it removes the ad-hoc resemblance with `--print=cfg` in order to output a simplified but still compatible `--check-cfg` arguments. The goal is to future proof the output of `--print=check-cfg` like `--check-cfg` is, and the best way to do that is to use it's syntax. This is particularly relevant for [RFC3905](rust-lang/rfcs#3905) which wants to introduce a new predicate: `version(...)`.
Rollup merge of #150840 - print-check-cfg-rework-output, r=nnethercote Make `--print=check-cfg` output compatible `--check-cfg` arguments This PR changes significantly the output of the unstable `--print=check-cfg` option. Specifically it removes the ad-hoc resemblance with `--print=cfg` in order to output a simplified but still compatible `--check-cfg` arguments. The goal is to future proof the output of `--print=check-cfg` like `--check-cfg` is, and the best way to do that is to use it's syntax. This is particularly relevant for [RFC3905](rust-lang/rfcs#3905) which wants to introduce a new predicate: `version(...)`.
| ### Why this design? | ||
| The syntax `rust_version >= "1.85"` is highly intuitive and directly expresses the user's intent. It is a general design that can be used to solve an entire class of adjacent problems, including platform versioning. It is a principled design, as by introducing a `version` type to the `cfg` system, we create a sound basis for comparison operators and other config types in the future. The syntax avoids the semantic confusion of proposals like `rust_version = "1.85"` which would have overloaded the meaning of `=` for a single special case. | ||
| This design directly solves the MSRV problem in a way that RFC 2523 did not. The fact that crates maintaining an MSRV will be able to adopt it for newer version constraints buys back some of the time that was spent designing and implementing the newer iteration of this feature.[^buy-back] While sometimes it is better to ship something functional quickly, the fact that users have an functional workaround in the form of build scripts pushes the balance more in the direction of waiting to deliver a high quality solution. |
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 "MSRV problem" is much smaller than this RFC makes it out to
- This feels contrary to the understanding I walked away from RustWeek and the initial revived discussions from RFC 2523. Even the conversations we've had in private last month about this. At those it made it sound like "we need this sooner than later to resolve the build script performance issue" while this is saying "we can take our time".
| Single-valued config types give us a chance to revisit some earlier decisions like the use of `=` in predicates. For now these are a hard error. Future extensions might add `==` comparisons with a more natural meaning for single-valued configs. | ||
| [^buy-back]: | ||
| A quick [sample][crate-sample] of two MSRV-preserving popular crates that already make use of feature gating, serde and proc-macro2, showed that those crates would be able to drop their build scripts roughly **a year earlier** with a solution that did not break MSRV compatibility. Obviously, this analysis is incomplete, but it has the benefit of emphasizing popular crates that show up in the critical path of many build graphs. |
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 seeing the logical connection between that link, the data, and this statement.
This RFC proposes a general mechanism for version-based conditional compilation called "typed cfgs".
Summary
This RFC proposes "typed
cfgs", a new form of conditional compilation predicate that understands types. Initially, this RFC proposes to add support for version-typedcfgs, allowing for ergonomic version comparisons against the language version supported by the compiler. This would be exposed through two new built-incfgnames:rust_version, which can be compared against a language version literal, e.g.,#[cfg(rust_version >= "1.85")].rust_edition, which can be compared against an edition literal, e.g.,#[cfg(rust_edition >= "2024")].This design solves a long-standing problem of conditionally compiling code for different Rust versions without requiring build scripts or forcing libraries to increase their Minimum Supported Rust Version (MSRV). It also replaces the
cfg(version(..))part of RFC 2523.History
There have been several previous attempts to solve the problem of conditional compilation by Rust version.
#[cfg(version(..))]. However, it left the syntax as an open question. Attempts to stabilize it were blocked because the new syntax would be a hard error on older compilers, defeating the goal of supporting older MSRVs.#[cfg(version_since(rust, "1.95"))]for Rust-version conditional compilation #3857 proposed#[cfg(version_since(rust, ...))], which solved the MSRV problem and generalized the mechanism beyond Rust versions while defining the interaction with command line flags like--check-cfg.This RFC takes the lessons from both previous attempts. It proposes a path to the ergonomic
rust_version >= "..."syntax that was preferred during language team discussions, while providing a clear MSRV-compatibility story from day one.The RFC also incorporates use cases from the
cfg_target_versionRFC (#3750), which proposed a way to compare against the version of the target platform's SDK (e.g.,#[cfg(target_version(macos >= "10.15"))]). Version-typed cfgs provide a path to supporting these comparions.Finally, it takes cues from previous discussions around mutually exclusive features and a
cfg_value!()macro, and lays out a path toward more single-valued config types that could support these features.Rendered