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.
Motivation
This RFC solves a class of problems requiring conditional compilation based on versions. Today, the only stable tool for this is a build script (build.rs), but these add significant compilation overhead, are clunky to write and maintain, and make it difficult to express ranged version constraints like "between versions 18 and 27".
In general, conditional compilation is required to do one of two things:
- Exposing new features when code is compiled with newer versions of the language or library while maintaining compatibility with older versions.
- Straddling breaking changes in an API, allowing code to compile against both the old and new versions, which are mutually incompatible.
We verify our solution by checking that it is general enough to handle three kinds of versions:
- The version of Rust, the language and standard library being used to compile the current crate.
- The version of the target platform the code is being compiled for.
- The version of dependencies, like system libraries and Rust crates.
We directly address the Rust version use case in this RFC, introducing the rust_version cfg as a tool for crates to expose new Rust features and rust_edition as a tool for straddling breaking changes to Rust itself. These are built atop a general mechanism for version-typed cfgs that can be used for target platforms and libraries.
This RFC proposes a solution that avoids these pitfalls, solves related versioning problems besides just the Rust version, and builds a scaffolding for related cfg features we might add in the future.
Example: Diagnostic attributes
One motivating example is making it ergonomic to adopt attributes that were stabilized after a crate's MSRV. For example, the #[diagnostic::on_unimplemented] attribute is a stable feature that library authors can use to provide better error messages. However, if a library has an MSRV from before this attribute was stabilized, they cannot use it without a build script. A build script is often too much overhead for such a small, non-essential feature.
This RFC makes it trivial to adopt even in a crate that doesn't want to use a build script. In this case, since the diagnostic attribute namespace landed before rust_version, you would write
With this feature we hope to see more people using useful attributes like on_unimplemented, even with MSRVs before when the diagnostic attribute namespace was added. Gated diagnostic attributes like this will not be active until the Rust version where this feature ships, but using them still adds value. While some crates must hold a low MSRV to allow building in environments with older compilers, like Linux distros, most active Rust development still takes place on recent compiler versions. Using this gating mechanism will mean that most users of a crate benefit from the attributes, without changing the crate's MSRV or using a build script.
Guide-level explanation
If your crate's MSRV is at least the version where typed cfgs were stabilized, you can directly use the version comparison. For example, imagine a new function pretty_print() is stabilized in Rust 1.92:
rust_version also allows the use of these predicates while maintaining a lower MSRV than the version rust_version itself ships in. The key is to first check for the existence of the rust_version configuration itself before trying to use it in a comparison.
This chained config pattern is only necessary when your MSRV straddles both the rust_version feature and a new feature that shipped after it.
Similarly, you can check the Rust edition:
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 generators like bindgen that need to produce different code depending on the edition context they are being used in.1
See this comment for a list of edition-related issues where this would have been helpful.
For this RFC, the only supported comparison operators are >= and <.
Maintaining an MSRV
Crates that want to maintain an MSRV are strongly encouraged to test against that MSRV in CI.
Version gates may cause warnings on older compilers. If you want your MSRV builds to build warning-free, allow the version_constraint_unknown_version lint by running cargo with this in your environment:
RUSTFLAGS="-A version_constraint_unknown_version"
If your MSRV is earlier than when rust_version was released and you use the "cfg stacking" method described above, additionally add this to your Cargo.toml:
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ["cfg(rust_version)"]
Reference-level explanation
This RFC adds a new kind of predicate to the cfg attribute, allowing for comparison against version identifiers.
Version predicates
A version predicate is available for cfg identifiers that are declared to be of type version.
The grammar for cfg option predicates will be expanded to the following:
ConfigurationOption ->
IDENTIFIER
| IDENTIFIER ConfigurationComparison ( STRING_LITERAL | RAW_STRING_LITERAL )
ConfigurationComparison ->
`=`
| `<`
| `>=`
The < and >= comparisons are only valid when the IDENTIFIER on the left-hand side names a defined cfg option of type version.
The = comparison is only valid when the option is undefined or of type default.
A cfg identifier is of type version if:
- It is one of the built-in identifiers
rust_versionorrust_edition. - It is declared with the special syntax
--cfg 'name=version("...")'and if using--check-cfgis declared appropriately with--check-cfg 'cfg(name, version())'.
The ident must be a known cfg identifier of type version. The literal must be a string literal that represents a valid version.
A version predicate evaluates to true if the comparison is true, and false otherwise. If the identifier is not a known version-typed cfg, or the literal is not a valid version string, a compile-time error is produced.
Version Literals
The STRING_LITERAL in a version predicate must conform to the following grammar:
version_literal :
VERSION_FIELD ('.' VERSION_FIELD)*
VERSION_FIELD :
'0'
| ('1'...'9') ('0'...'9')*
This grammar defines a version as one or more non-negative integer fields separated by dots. Each field must not have leading zeros, unless the field itself is 0. For example, "1.90" and "0.2.0" are valid, but "1.09" is not.
The comparison is performed field-by-field, filling in any missing fields 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.
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.
Using version-typed config values with the = predicate results in a hard error.
Version-typed cfgs as options
When cfg option with a version type and value is used as a bare option, it evaluates to true:
Builtin version-typed cfgs
rust_version
The rust_version cfg is version typed and contains two fields (major and minor version). This may be expanded to all three fields in the future.
A lint will be issued if rust_version is compared to more than two fields (e.g., "1.92.0") of a version equal to or earlier than the current compiler. This is because language features should not depend on patch releases. However, we only lint on "known" versions in case we decide to include all three fields in the future.
A new lint 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). This lint may be expanded to include user-defined cfgs when check-cfg supports specifying useful ranges.
Rust pre-releases
This RFC does not specify how "nightly" compilers with pre-release versions of the language are handled, i.e. whether they are marked as being the previous version or the "upcoming" version. That may change without breaking Rust's stability guarantees.
Note: The history of this question is covered in RFC 3857.
rust_edition
The rust_edition cfg is version typed and contains one field, the year of the edition.
A lint will be issued if the literal of a known edition has more than one field, or if we know the value is never going to be a Rust edition (for example, "2019").
Defining version-typed configs
To define a version-typed cfg, the following syntax must be used. version() must contain a valid version_literal.
This can also be used to override built-in version cfgs (e.g. --cfg 'rust_version=version("1.50.0")'), which is primarily useful for testing.
- If a version cfg is used with a string literal in a comparison that is not a valid version string, a hard error is emitted.
- If a cfg that is not a version type is used in a version comparison, a hard error is emitted. For undefined cfgs, this could be relaxed to evaluate to false in the future.
- If a cfg that is a version type is used in a non-version comparison (
=), a hard error is emitted. - Version typed cfgs are single-valued. Setting more than one value for the flag is a hard error. This includes values of other types, so given the example above, adding both
--cfg my_app_versionand--cfg my_app_version="foo"would cause a hard error. - Setting the same value multiple times on the command line can be a hard error initially. This is a conservative choice that the compiler team may choose to relax, e.g. for build system integration reasons.
Configs defined using the existing command-line syntax --cfg 'name="value"' have the default config type. The name of this type is not user-facing and may change.
Interaction with other compiler flags
-
--check-cfg: To inform the compiler that acfgis expected to be a version, and to enable linting, use: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).
-
--print cfg: User-defined version cfgs are printed in thename=version("...")format. Whether to print the built-inrust_versionandrust_editioncfgs is left as an unresolved question to be determined based on tool compatibility. In future editions, the builtin cfgs should always be printed. -
--print check-cfg: The built-inrust_versionandrust_editioncfgs are implicitly included, sorustc --print=check-cfgwill always list them. We can add these immediately because--print check-cfgis unstable.
Stabilization
The features described in this RFC may be stabilized in phases:
- The initial stabilization can include the built-in
rust_versionandrust_editioncfgs and the ability to compare them with>=and<. - The ability for users to define their own
version-typedcfgs via--cfgand--check-cfgcan be stabilized later.
This approach delivers the most critical functionality to users quickly, while allowing more time to finalize the design for user-defined version predicates.
Lint names
useless_rust_version_constraint(deny by default): A Rust version constraint will always be true or false because it names a version prior to when this feature stabilized.version_constraint_unknown_version(warn by default): Arust_editionorrust_versionconstraint falls outside the set of values known to this compiler. This includes future versions.version_constraint_wrong_precision(warn by default): A version constraint's precision level falls outside the expected range, e.g.rust_version >= "1"orrust_edition >= "2015.0".
Note that the first lint is specific to rust_version, while the remaining lints can be generalized to any version constraint. The reason to have a lint specific to rust_version is that we can say for certain whether it will be true, which can impact the severity of the lint.
This section is subject to change prior to stabilization.
Drawbacks
- Making the perfect the enemy of the good. RFC 2523 was accepted, and an implementation of its
version()predicate is ready. - Increased compiler complexity. This introduces a new concept of "typed"
cfgs 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 MSRV-preserving pattern still does not allow using the feature to check for versions prior to when this feature was introduced.
- 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 theis_terminal_polyfillcrate does. - Conditional compilation adds testing complexity. In practice, most crate maintainers only test their MSRV and the latest stable. Note that tools like
cargo hackmake testing across a range of versions easier. - This does not support branching on specific nightly versions. rustversion supports this with syntax like
#[rustversion::since(2025-01-01)].
Rationale and alternatives
History
This RFC builds on insights from previous work:
RFC #2523 introduced #[cfg(version(1.xx))] and established the idea of conditional compilation based on the language version.
RFC #3857 proposed #[cfg(version_since(rust, ...))], which solved the MSRV problem by allowing #[cfg(rust)] and generalized the mechanism beyond Rust versions, while defining the interaction with command line flags like --check-cfg.
RFC (#3750) cfg_target_version proposed a way to compare against the target platform's minimum supported OS version (e.g., #[cfg(target_version(macos >= "10.15"))]), which was used to validate this design.
Pre-RFC Mutually-exclusive, global features described a Cargo use case for single-valued config types as well as a cfg_value!() macro.
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.2 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.
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.
This RFC proposes to lint on any unknown version, including future versions and editions of Rust, with the version_constraint_unknown_version lint. This helps prevent mistakes and accidental "syntax bombs" where a piece of code is never compiled until a future version of Rust, at which point it introduces a compiler error. The recommendation for testing older compilers in CI is to disable this lint for older compilers specifically.
A quick 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.
Shipping an MSRV-incompatible feature sooner would allow immediate use by non-MSRV-preserving crates. Picking the MSRV-compatible option later allows crates that do not make use of feature gating with build scripts today to begin feature gating as soon as rust_version ships, without introducing build scripts and without bumping their MSRV.
Alternative 1: #[cfg(version(1.85))] (RFC 2523)
This was the original accepted RFC for version-based conditional compilation.
Rationale for not choosing
The syntax in the RFC is an error on older compilers, meaning a library would first have to bump its MSRV to the version that introduced the syntax. After this issue was pointed out on the RFC, the syntax was left as an open question that was not revisited by the lang team between the RFC merging in 2019 and a stabilization discussion in 2025.
The current lang team did not reach consensus on the syntax from the RFC. Several problems were identified:
- The word
versiondoes not sufficiently communicate that it's the Rust version we're talking about. - The mechanism is special-purpose and geared toward one use case (detecting the Rust version).
- The function-call syntax, chosen for consistency with
cfg(accessible()), isn't obvious enough in its meaning and does not cleanly extend to new kinds of comparisons. A recent poll of the lang team showed that most people opposed extending that syntax to include other kinds of comparisons within the quotes, likeversion("< 1.2.3"). At the same time, it adds another level of nested parantheses, which can be hard for humans to parse. - Crates supporting old MSRVs won't be able to use the feature until bumping their MSRV. (Note that even in this RFC, crates with existing feature gates using build scripts won't be able remove their build scripts until the last legacy feature gate falls outside their MSRV window.)
- The RFC was accepted more than 6 years ago. During this time we've learned about more adjacent use cases and directions we would like to evolve the language. If designed today, the feature would look much more like this RFC than RFC 2523.
Alternative 2: #[cfg(rust_version = "1.85")] (meaning >=)
This syntax is parseable by older compilers, which is a significant advantage for MSRV compatibility.
Rationale for not choosing
The use of = was highly controversial. In Rust today, the cfg syntax has two conceptual models: "set inclusion" for multi-valued cfgs (e.g., feature = "serde") and "queries" for boolean flags (e.g., unix). However, people tend to think of = more as "equality" than as "set inclusion", and the use of = for versions strongly implies exact equality (== 1.85).
Likening >= to set inclusion makes sense in the narrow context of Rust versions, which do not have semver-incompatible changes, but it does not generalize well to versions that do. Checking the compiler version is is really a comparison between two values, not a check for inclusion in a set.
The interaction with --print cfg was unclear. See RFC 3857 for more context.
Advantage
This approach could potentially be made to work inside Cargo.toml (e.g., for conditional dependencies), which currently cannot use the stacked-cfg trick. However, the disadvantages in terms of semantic clarity for the language itself outweigh this benefit for an in-language feature.
Alternative 3: #[cfg(version_since(rust, "1.85"))] (RFC 3857)
This alternative also avoids the MSRV problem and is extensible, similar to the current proposal.
Rationale for not choosing
While a good design, the "typed cfgs" approach with an actual comparison operator (>=, <) is arguably more natural and ergonomic. A language team poll indicated a preference for rust_version >= "1.85" if it could be made to work. This RFC provides the mechanism to make it work in a principled way.
The next runner up in the poll3 was a tweaked version of this syntax, version(rust, since = "1.85"), and this version may have been accepted. However, the RFC was closed by the author before this change was made. After making some effort to resurrect it, the author of the current RFC decided to pursue the direction in this RFC instead.
The poll was conducted using a Condorcet voting method that asked team members to rank their choices from most to least preferred. This runner up did not represent the preferred syntax of every individual on the team.
Prior art
Parts of this section are adapted from RFC 3857.
Rust Ecosystem
There are very widely used crates designed to work around the lack of native version-based conditional compilation. These rely on build scripts to detect the compiler version and set custom cfg flags. rustversion also has a proc macro component for the nicest user experience.
rustversion: A popular proc-macro (over 260 million downloads) that allows checks like#[rustversion::since(1.34)]. It supports channel checks (stable, beta, nightly), equality, and range comparisons.- Build Script Helpers: Crates like
rustc_versionandversion_checkare widely used inbuild.rsscripts to query the compiler version and emitcargo:rustc-cfginstructions. They provide programmatic access to version numbers, channels, and release dates. - Release Info: Crates like
shadow-rsandvergenexpose build information, including the compiler version, to the compiled binary. - Polyfills: Some crates, like
is_terminal_polyfill, maintain separate versions for different MSRVs, relying on Cargo's MSRV-aware resolver to select the correct implementation.
This RFC aims to obviate the need for these external dependencies for the common case of checking the language version, reducing build times and complexity.
Cargo
rust-version: The[package]section ofCargo.tomlcan specify arust-versionfield. This allows Cargo to select appropriate versions of dependencies and fail early if the compiler is too old. However, it does not provide fine-grained, in-code conditional compilation. This RFC brings a similar capability directly into the language, but for controlling code within a crate rather than for dependency resolution.
Other languages
-
Swift (
#if compiler,#if swift): Swift provides platform conditions for both the compiler version and the language mode.compiler(>=5)checks the version of the compiler.swift(>=4.2)checks the active language version mode.- These support
>=and<operators, similar to this proposal.
-
C++ (
__cplusplus): The C++ standard defines the__cplusplusmacro, which expands to an integer literal that increases with each new version of the standard (e.g.,201103Lfor C++11,202002Lfor C++20). This allows for preprocessor checks like#if __cplusplus >= 201103L. This is very similar to therust_version >= "..."proposal in that it uses standard comparison operators against a monotonically increasing value. However, it is less granular, as several years pass between new C++ versions. -
Clang/GCC (
__has_feature,__has_attribute): These function-like macros allow for checking for the presence of specific compiler features, rather than the overall language version. For example,__has_feature(cxx_rvalue_references)checks for a specific language feature. This approach is more granular but also more verbose if one needs to check for many features at once. This approach was discussed in RFC #2523, but rejected, in part because we wanted to reinforce the idea of Rust as "one language" instead of a common subset with many compiler-specific extensions. -
Python (
sys.version_info): Python exposes its version at runtime viasys.version_info, a tuple of integers(major, minor, micro, ...). Code can check the version with standard tuple comparison, e.g.,if sys.version_info >= (3, 8):. This field-wise comparison is very similar to the logic proposed in this RFC. However, because Python is interpreted, a file must be syntactically valid for the interpreter that is running it, which makes it difficult to use newer syntax in a file that must also run on an older interpreter. Rust, being a compiled language with a powerful conditional compilation system, does not have this limitation, and this RFC's design takes full advantage of that.
Versioning systems
Not every system uses Rust's standard three-part semver versioning scheme, but many are close. In this section are examples of more bespoke versioning systems that this feature can accommodate. The "escape hatch" for when your version numbers are not semver like is to split them into different version cfgs, each of which is semver like (in the simplest case, just a number).
- Chromium: Chromium's version format is a four-part number: MAJOR.MINOR.BUILD.PATCH, where MAJOR increments with significant releases, MINOR is often 0, BUILD tracks trunk builds, and PATCH reflects updates from a specific branch, with BUILD and PATCH together identifying the exact code revision. (Thanks to Jacob Lifshay on github.)
Operating systems
Operating systems include many versions, including kernel versions, public OS version, and system API versions. Usually API versions are the most relevant for conditional compilation. Most APIs are preserved across versions. Some operating systems, like Windows, prioritize backward compatibility of applications, while others balance backward compatibility with the deprecation and removal of APIs.
- Windows API version: XP is 5.1, Vista is 6.0, 7 is 7.0, 7 with Service Pack 1 is 7.1, 8 is 8.0, 8.1 is 8.1 and Windows 10/11 currently ranges from 10.0.10240 to 10.0.26200. There are "friendlier" names such as 1507 for 10.0.10240 but I think those are better done as some kind of cfg alias rather than being built-in. (Thanks to Chris Denton on zulip.)
- Android API level: Single, sequential integer value like "34", "35".
- macOS version: Based on the operating system version; there is not a separate API level concept. In general these use multi-part versions like "10.15". Starting with macOS 11.0, the major version number has increased with each new version and the second field has been 0.
- Fuchsia API version: Single number like "30", similar to Android, but released on a cadence closer to Rust's 6-week release cadence. Fuchsia itself uses Rust along with some build system hacks to express predicates like
fuchsia_api_level_less_than = "20". (Thanks to Hunter Freyer on zulip.)
Systems that fall outside this RFC
- Python packages: Python's package versioning system is defined in PEP 440 to have five segments, only one of which (the release segment) is directly supported in this RFC. This level of flexibility seems to exist because it was designed to cover the many diverging versioning schemes used by Python packages at the time it was introduced. (Thanks to Ed Page on github.)
Public version identifiers are separated into up to five segments:
- Epoch segment:
N!- Release segment:
N(.N)*- Pre-release segment:
{a|b|rc}N- Post-release segment:
.postN- Development release segment:
.devN
Unresolved questions
- Should the builtin
rust_versionandrust_editionbe printed with--print cfgon the command line? We'd like the eventual answer to be "yes", but existing tools that parse the output might break with the newrust_version=version("1.99")syntax. If we can manage the breakage we should; otherwise we can gate it on a future edition.- Note: Using editions requires being careful about passing
--editiontorustc --print cfginvocations, whichcargofor example does not currently do. This can introduce unexpected inconsistencies.
- Note: Using editions requires being careful about passing
- Cargo team: How should
cargoexpose version-typedcfgs to build scripts? Should--cfg foo=version("1.0")result inCARGO_CFG_FOO=1.0orCARGO_CFG_FOO=version("1.0")? This is technically out of scope for this RFC but important for the ecosystem.
Future possibilities
- More expressive check-cfg:, including
--check-cfg 'cfg(foo, version("2018", "2022", "2025"))': supported versions (discrete)--check-cfg 'cfg(foo, version(values >= "1.75"))': supported versions (range)--check-cfg 'cfg(foo, version(fields <= 2))': max supported precision within a version
- "Compatible-with" operator: We can introduce a
~=operator that works like Cargo's caret requirements. For example,cfg(some_dep ~= "1.5")would be equivalent tocfg(all(some_dep >= "1.5", some_dep < "2.0")). The rationale for not doing this now is that it's easy enough to write by hand. - More comparison operators: While this RFC only proposes
>=and<, the underlyingversiontype makes it natural to add support for<=,==,!=, etc., in the future. - Dependency Version
cfgs: The "typedcfg" infrastructure can be extended to query the versions of direct dependencies, e.g.,#[cfg(serde >= "1.0.152")]. This would require significant integration with Cargo. - System library versions supplied by
syscrates: Cargo can allowsyscrates to expose the versions of their system libraries to dependents as version-typed cfgs. - Builtin platform version
cfgs: Rust can add target version configs like#[cfg(macos_target_version >= "26.0")]on a platform-specific basis. - Other
cfgtypes: We can introduce other types, such as integers (mutli- or single-valued) and single-valued strings. This can be useful for a variety of features, from system library versioning schemes (kconfig) to enabling things like mutually exclusive global features. - Namespaced
cfgs: We can group Rust-specificcfgs under arust::namespace, e.g.,#[cfg(rust::version >= "1.85")]. This RFC intentionally keepsrust_versionat the top level to simplify the initial implementation and stabilization, but namespacing can be explored in the future to better organize the growing number of built-incfgs. - Macro that evaluates to a cfg value: We can add a
cfg_value!()macro for single-valued configs that evaluates to its value. For versions this could evaluate to a string literal. - Short-circuiting
cfgpredicates: Changeanyandallpredicates to short-circuit instead of evaluating all their arguments. This would make introducing new predicates and comparison operators much easier. - Const eval in
cfg: In the futurecfgcan be expressed entirely in terms of const eval, with a simple macro desugaring for cfg-specific predicates into proper Rust expressions. This RFC is compatible with that vision; see this demo of how the desugaring can work. cfg_alias:#[cfg_alias](RFC 3804) would allow the use of named identifiers for features instead of using raw version numbers throughout the codebase.
Pre-releases
The version string parsing can be extended to support pre-release identifiers (-beta, -nightly), though this adds complexity to the comparison logic. RFC 3857 discusses this possibility for generic versions as well as for the language itself.
Like 3857, this RFC does not support prereleases. It assumes prereleases info will be stripped from version numbers.
If we decide to support prereleases in the future, they can be introduced over an edition. In older editions all predicates would be interpreted as if they had -0, or the smallest possible prerelease identifier, appended to them. For example:
cfg(foo_version >= "1.5")would be interpreted ascfg(foo_version >= "1.5-0")cfg(foo_version < "2.0")would be interpreted ascfg(foo_version < "2.0-0")
This is so any version that previously had its prerelease info stripped can now add it without changing the meaning in existing code.
To see why an edition is necessary, consider how we would handle the predicate cfg(foo_version >= "2.0") when the actual version is 2.0-alpha. This predicate would go from true to false once prereleases were accepted and passed on the command line.
Technically this can be handled by the build system deciding what version to pass based on the edition. Decoupling the language and build system in this way would add flexibility for code using non-cargo build systems to migrate their code and adopt prereleases sooner. This seems like a small benefit; many projects using non-cargo build systems use a monorepo and wouldn't benefit from this feature. If we later decide to do something like this we can add support for prereleases to the language, but with a lint that tells you they are useless with cargo until the next edition. Otherwise, since we would need to add support for prereleases in the language, it's cleaner to handle the edition migration within the language as well.