Skip to content

Conversation

epage
Copy link
Contributor

@epage epage commented Sep 15, 2025

Tracking issue (#[cfg(version(..))]

Rendered

Summary

Allow Rust-version conditional compilation by adding a built-in --cfg rust=<version> and a minimum-version #[cfg] predicate.

Say this was added before 1.70, you could do:

[target.'cfg(not(since(rust, "1.70")))'.dependencies"]
is-terminal = "0.4.16"
fn is_stderr_terminal() -> bool {
    #[cfg(since(rust, "1.70"))]
    use std::io::IsTerminal as _;
    #[cfg(not(since(rust, "1.70")))]
    use is_terminal::IsTerminal as _;

    std::io::stderr().is_terminal()
}

This supersedes the cfg_version subset of RFC 2523.

@epage epage added the T-lang Relevant to the language team, which will review and decide on the RFC. label Sep 15, 2025
@epage
Copy link
Contributor Author

epage commented Sep 15, 2025

RE my assumptions around owning teams for this decision:

  • T-lang: primarily geared towards them
  • T-compiler: unsure if any of the parts related to the compiler are sufficient for adding T-compiler.
  • T-clippy: the clippy sections are written with the intention of looking at the whole picture and call out that they are not prescriptive and I don't think T-clippy needs to be pulled into the decision though input is warranted
  • T-cargo: this is an area where Cargo just copies what T-lang/T-compiler do and are not co-owners but input is warranted

CC @rust-lang/compiler , @rust-lang/clippy , @rust-lang/cargo

@epage
Copy link
Contributor Author

epage commented Sep 15, 2025

CC @Urgau as this touches a lot on check-cfg and would appreciate your input on how all of that is handled in this.

# Drawbacks
[drawbacks]: #drawbacks

People may be using `--cfg rust` already and would be broken by this change.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

At least with my initial search, I did not see any public uses


This does not help with probes for specific implementations of nightly features which still requires having a `build.rs`.

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

Choose a reason for hiding this comment

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

I assume you mean because of them using #[cfg(since(rust, "1.$BIG_NUMBER"))] or similar "point a loaded firearm directly at my lower limb" gestures? They sort of already can do that, with the classic example being mem::transmute of std entities with entirely-unspecified layouts, and then us changing it in a future version. But yes, strictly speaking it does introduce a new vector, and one we will inevitably hit in time rather than merely potentially.

Copy link

Choose a reason for hiding this comment

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

I wonder if this is something that could be warn-by-default. Or at least warn-by-default in clippy. The warning would only be an issue when a workspace is worked on by rust versions older than their highest version cfg.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we have any way of detecting this. No toolchain can flag "too new" version requirements, because it's reasonable to detect things newer than that toolchain.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also I would assume this problem already exists with rustversion.

Copy link
Member

@workingjubilee workingjubilee Sep 18, 2025

Choose a reason for hiding this comment

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

Yes. I expect it will be very common to have a crate that is developed and tested primarily with an old rustc version and then have since used against a much later one so that new features can also be used selectively, so warning against this issue will mostly interfere with what may be the most popular pattern for using the feature.

Comment on lines +308 to +309
*but* maybe that would just be the start to natively supporting more types in `cfg`,
like integers which are of interest to embedded folks.
Copy link
Member

Choose a reason for hiding this comment

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

It feels somewhat unclear to me why we would start this journey by supporting this new "version literal" type in cfg only and not in the language? As opposed to supporting integers first, which are supported in the language.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that I'm saying doing so is a potential alternative; I am not proposed we do. If we did so, the reason would be "because this RFC came first".

Comment on lines 596 to 599
- Contort the version into SemVer
- This can run into problems either with having more precision (e.g. `120.0.1.10` while SemVer only allows `X.Y.Z`) or post-release versions (e.g. `1.2.0.post1` which a SemVer predicate would treat as a pre-release).
- Add an optional third field for specifying the version format (e.g. `#[cfg(since(windows, "10.0.10240", <policy-name>)]`)
- Make `--check-cfg` load-bearing by having the version policy name be specified in the `--check-cfg` predicate
Copy link
Member

Choose a reason for hiding this comment

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

One depressingly verbose option, but one that will work without modification to the proposal, is to attach an additional "epoch version" via cfg, e.g.

#[cfg(
    all(
        since(thing, "1.0.0"), since(thing_epoch, "2")
    )
)]
mod something {}

#[cfg(
    all(
        since(thing, "1.1.0"), not(since(thing_epoch, "2"))
    )
)]
mod something {}

Obviously, since(thing, "1.0.0") would be satisfied by "1.1.0" in ordering without the all including an additional "version epoch".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unsure how much we need to explore this as its a future possibility. The main question is whether we paint ourselves into a corner for target_version.

@davidhewitt
Copy link
Contributor

If I'm reading the RFC right, this attribute is very interesting in that it generalizes to other external dependencies too.

In PyO3, we have to deal with a ton of variation for different Python versions. Our current solution is to emit Py_3_8, Py_3_9 cfgs for Python 3.8, 3.9 etc. I think that #[cfg(since(python, "3.8"))], #[cfg(since(python, "3.9"))] would become possible with this RFC?

I wonder if this would immediately also follow for cargo dependencies too. E.g. #[cfg(since(hashbrown, "0.15"))], #[cfg(since(hashbrown, "0.16"))] might make it easier to support range dependencies like hashbrown = ">= 0.14, < 0.17".

@epage
Copy link
Contributor Author

epage commented Sep 17, 2025

In PyO3, we have to deal with a ton of variation for different Python versions. Our current solution is to emit Py_3_8, Py_3_9 cfgs for Python 3.8, 3.9 etc. I think that #[cfg(since(python, "3.8"))], #[cfg(since(python, "3.9"))] would become possible with this RFC?

Currently, the RFC requires SemVer versions. I am exploring the future possibility that will relax that and seeing what impact that has on core proposal

I wonder if this would immediately also follow for cargo dependencies too. E.g. #[cfg(since(hashbrown, "0.15"))], #[cfg(since(hashbrown, "0.16"))] might make it easier to support range dependencies like hashbrown = ">= 0.14, < 0.17".

Cargo providing dependency versions to the compiler is listed as a future possibility.

```

When evaluating `since`,
1. If the string literal does not conform to the syntax from `<major>` to `<major>.<minor>.<patch>-<pre-release>` where the first three fields must be integers, this will evaluate to `false`.<br>
Copy link
Member

Choose a reason for hiding this comment

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

An invalid SemVer syntax for the string literal should probably be a compile time error, that would allow for future extension without being breaking changes.

Copy link
Member

Choose a reason for hiding this comment

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

Another reason for making it a error would be to avoid not(since(foo, "<invalid>")) always returning true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

940b979 turned them into errors and 75c486b added the not rationale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-lang-radar Items that are on lang's radar and will need eventual work or consideration. T-compiler Relevant to the compiler team, which will review and decide on the RFC. 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.