Skip to content

Conversation

@tmandry
Copy link
Member

@tmandry tmandry commented Jan 7, 2026

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-typed cfgs, allowing for ergonomic version comparisons against the language version supported by the compiler. This would be exposed through two new built-in cfg names:

  • 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.

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_version RFC (#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

@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jan 7, 2026
Copy link
Contributor

@weiznich weiznich left a 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)

Comment on lines +79 to +85
#[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.
Copy link
Contributor

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)

Copy link
Contributor

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:

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.

@tgross35
Copy link
Contributor

tgross35 commented Jan 7, 2026

(Obvious disclaimer: I'm not associated with any team, that's just my personal opinion, so feel free to ignore that)

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 ❤️

@@ -0,0 +1,265 @@
- Feature Name: typed_cfgs
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Member

@yoshuawuyts yoshuawuyts Jan 8, 2026

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.

Copy link
Contributor

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_version should 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.

Copy link
Contributor

@traviscross traviscross Jan 9, 2026

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")]

Copy link
Member Author

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.

Copy link
Member Author

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.

Comment on lines +285 to +286
# Future possibilities
[future-possibilities]: #future-possibilities
Copy link
Contributor

Choose a reason for hiding this comment

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

- 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.
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

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

- 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
Copy link
Contributor

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

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.
Copy link
Contributor

@traviscross traviscross Jan 10, 2026

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.

* 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"`).
Copy link
Contributor

@traviscross traviscross Jan 10, 2026

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.

Copy link
Contributor

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.

cc @joshtriplett

Copy link
Member Author

@tmandry tmandry left a 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.

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.
Copy link
Member Author

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.

Comment on lines 141 to 143
* 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"`).
Copy link
Member Author

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.

* 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.
Copy link
Member Author

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.

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.
Copy link
Member Author

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.

--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())`.
Copy link
Member Author

@tmandry tmandry Jan 13, 2026

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.

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.
Copy link
Member Author

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.

Comment on lines +127 to +133
```text
version_literal :
NUMERIC_COMPONENT ('.' NUMERIC_COMPONENT)*
NUMERIC_COMPONENT :
'0'
| ('1'...'9') ('0'...'9')*
Copy link
Contributor

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.

Comment on lines +291 to +294
- **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))'`
Copy link
Contributor

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

Suggested change
- **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.
Copy link
Contributor

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 true because the pre-release is ignored
  • When pre-releases are supported, would evaluate to false because all pre-releases are less than non-pre-releases.


* 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).
Copy link
Contributor

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))

* **`--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`.
Copy link
Contributor

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 version type 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.
Copy link
Contributor

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?

Comment on lines +285 to +286
- 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.
Copy link
Contributor

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?

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Jan 13, 2026
…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(...)`.
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Jan 13, 2026
…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(...)`.
rust-timer added a commit to rust-lang/rust that referenced this pull request Jan 14, 2026
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. The "MSRV problem" is much smaller than this RFC makes it out to
  2. 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.
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-lang Relevant to the language team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.