From 304fefce0fc6cb9b800a42fdaa50f62d585ad684 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 23 Apr 2025 19:41:43 -0400 Subject: [PATCH 1/4] Introduce an RFC for config aliases --- text/0000-cfg-alias.md | 165 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 text/0000-cfg-alias.md diff --git a/text/0000-cfg-alias.md b/text/0000-cfg-alias.md new file mode 100644 index 00000000000..b48cffc014f --- /dev/null +++ b/text/0000-cfg-alias.md @@ -0,0 +1,165 @@ +- Feature Name: `cfg_alias` +- Start Date: 2025-04-23 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: + [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary + +[summary]: #summary + +This RFC introduces a way to name configuration predicates for easy reuse +throughout a crate. + +```rust +#![cfg_alias( + x86_linux, + all(any(target_arch = "x86", target_arch = "x86_64"), target_os = "linux") +)] + +#[cfg(x86_linux)] +fn foo() { /* ... */ } + +#[cfg(not(x86_linux))] +fn foo() { /* ... */ } +``` + +# Motivation + +[motivation]: #motivation + +It is very common that the same `#[cfg(...)]` options need to be repeated in +multiple places. Often this is because a `cfg(...)` needs to be matched with a +`cfg(not(...))`, or because code cannot easily be reorganized to group all code +for a specific `cfg` into a module. The solution is usually to copy a `#[cfg]` +group around, which is error-prone and noisy. + +Adding aliases to config predicates reduces the amount of code that needs to be +duplicated, and giving it a name provides an easy way to show what a group of +configuration is intended to represent. + +Something to this effect can be done using build scripts. This requires reading +various Cargo environment variables and potentially doing string manipulation +(for splitting target features), so it is often inconvenient enough to not be +worth doing. Allowing aliases to be defined within the crate and with the same +syntax as the `cfg` itself makes this much easier. + +# Guide-level explanation + +[guide-level-explanation]: #guide-level-explanation + +There is a new crate-level attribute that takes a name and a `cfg` predicate: + +```rust +#![cfg_alias(some_alias, predicate)] +``` + +`predicate` can be anything that usually works within `#[cfg(...)]`, including +`all`, `any`, and `not`. + +Once an alias is defined, `name` can be used as if it had been passed via +`--cfg`: + +```rust +#[cfg(some_alias)] +struct Foo { /* ... */ } + +#[cfg(not(some_alias))] +struct Foo { /* ... */ } + +#[cfg(all(some_alias, target_os = "linux"))] +fn bar() { /* ... */ } +``` + +# Reference-level explanation + +[reference-level-explanation]: #reference-level-explanation + +The new crate-level attribute is introduced: + +```text +CfgAliasAttribute: + cfg_alias(IDENTIFIER, ConfigurationPredicate) +``` + +The identifier is added to the `cfg` namespace. It must not conflict with any +builtin configuration names, or with those passed via `--cfg`. + +Once defined, the alias can be used as a regular predicate. + +The alias is only usable after it has been defined. For example, the following +will emit an unknown configuration lint: + +```rust +#![cfg_attr(some_alias, some_attribute)] +// warning: unexpected_cfgs +// +// The lint could mention that `some_alias` was found in the +// crate but is not available here. + +#![cfg_alias(some_alias, true)] +``` + +RFC Question: + +Two ways to implement this are with (1) near-literal substitution, or (2) +checking whether the alias should be set or not at the time it is defined. Is +there any user-visible behavior that would make us need to specify one or the +other? + +If we go with the first option, we should limit to a single expansion to avoid +recursing (as is done for `#define` in C). + +# Drawbacks + +[drawbacks]: #drawbacks + +- This does not support more general attribute aliases, such as + `#![alias(foo = derive(Clone, Copy, Debug, Default)`. This seems better suited + for something like `declarative_attribute_macros` in [RFC3697]. + +[RFC3697]: https://github.com/rust-lang/rfcs/pull/3697 + +# Rationale and alternatives + +[rationale-and-alternatives]: #rationale-and-alternatives + +- The syntax `cfg_alias(name, predicate)` was chosen for similarity with + `cfg_attr(predicate, attributes)`. Alternatives include: + - `cfg_alias(name = predicate)` +- It may be possible to have `#[cfg_alias(...)]` work as an outer macro and only + apply to a specific scope. This likely is not worth the complexity. + +# Prior art + +[prior-art]: #prior-art + +In C it is possible to modify the define map in source: + +```c +# if (defined(__x86_64__) || defined(__i386__)) && defined(__SSE2__) +#define X86_SSE2 +#endif + +#ifdef X86_SSE2 +// ... +#endif +``` + +# Unresolved questions + +[unresolved-questions]: #unresolved-questions + +Questions to resolve before this RFC could merge: + +- Which syntax should be used? +- Substitution vs. evaluation at define time (the question under the + reference-level explanation) + +# Future possibilities + +[future-possibilities]: #future-possibilities + +- A `--cfg-alias` CLI option would provide a way for Cargo to interact with this + feature, such as defining config aliases in the workspace `Cargo.toml` for + reuse in multiple crates. From 780ccd7a2f77a5fe158a05d38b395f701d313032 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 23 Apr 2025 19:46:37 -0400 Subject: [PATCH 2/4] cfg-alias: Update with the PR number --- text/{0000-cfg-alias.md => 3804-cfg-alias.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-cfg-alias.md => 3804-cfg-alias.md} (98%) diff --git a/text/0000-cfg-alias.md b/text/3804-cfg-alias.md similarity index 98% rename from text/0000-cfg-alias.md rename to text/3804-cfg-alias.md index b48cffc014f..4a2b55a0a42 100644 --- a/text/0000-cfg-alias.md +++ b/text/3804-cfg-alias.md @@ -1,6 +1,6 @@ - Feature Name: `cfg_alias` - Start Date: 2025-04-23 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3804](https://github.com/rust-lang/rfcs/pull/3804) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) From 47d8ed18a28095c3cfdbca4f21a7cdb24da16f74 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 23 Apr 2025 20:55:41 -0400 Subject: [PATCH 3/4] Update syntax to `=`, add another usecase to motivation, mention --check-cfg --- text/3804-cfg-alias.md | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/text/3804-cfg-alias.md b/text/3804-cfg-alias.md index 4a2b55a0a42..a36354cfda6 100644 --- a/text/3804-cfg-alias.md +++ b/text/3804-cfg-alias.md @@ -12,10 +12,9 @@ This RFC introduces a way to name configuration predicates for easy reuse throughout a crate. ```rust -#![cfg_alias( - x86_linux, - all(any(target_arch = "x86", target_arch = "x86_64"), target_os = "linux") -)] +#![cfg_alias(x86_linux = all( + any(target_arch = "x86", target_arch = "x86_64"), target_os = "linux" +))] #[cfg(x86_linux)] fn foo() { /* ... */ } @@ -44,6 +43,22 @@ various Cargo environment variables and potentially doing string manipulation worth doing. Allowing aliases to be defined within the crate and with the same syntax as the `cfg` itself makes this much easier. +Another benefit is the ability to easily adjust configuration to many different +areas of code at once. A simple example is gating unfinished code that can be +toggled together: + +```rust +#![cfg_alias(todo = false)] // change `false` to `true` to enable WIP code + +#[cfg(todo)] +fn to_be_tested() { /* ... */ } + + +#[test] +#[cfg(todo)] +fn test_to_be_tested() { /* ... */ } +``` + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -51,7 +66,7 @@ syntax as the `cfg` itself makes this much easier. There is a new crate-level attribute that takes a name and a `cfg` predicate: ```rust -#![cfg_alias(some_alias, predicate)] +#![cfg_alias(some_alias = predicate)] ``` `predicate` can be anything that usually works within `#[cfg(...)]`, including @@ -79,11 +94,11 @@ The new crate-level attribute is introduced: ```text CfgAliasAttribute: - cfg_alias(IDENTIFIER, ConfigurationPredicate) + cfg_alias(IDENTIFIER `=` ConfigurationPredicate) ``` The identifier is added to the `cfg` namespace. It must not conflict with any -builtin configuration names, or with those passed via `--cfg`. +builtin configuration names, or with those passed via `--cfg` or `--check-cfg`. [^check-cfg] Once defined, the alias can be used as a regular predicate. @@ -97,7 +112,7 @@ will emit an unknown configuration lint: // The lint could mention that `some_alias` was found in the // crate but is not available here. -#![cfg_alias(some_alias, true)] +#![cfg_alias(some_alias = true)] ``` RFC Question: @@ -110,6 +125,9 @@ other? If we go with the first option, we should limit to a single expansion to avoid recursing (as is done for `#define` in C). +[^check-cfg]: `--check-cfg` is included here because it indicates there may be a + corresponding `--cfg`. + # Drawbacks [drawbacks]: #drawbacks @@ -124,9 +142,10 @@ recursing (as is done for `#define` in C). [rationale-and-alternatives]: #rationale-and-alternatives -- The syntax `cfg_alias(name, predicate)` was chosen for similarity with - `cfg_attr(predicate, attributes)`. Alternatives include: - - `cfg_alias(name = predicate)` +- The syntax `cfg_alias(name = predicate)` was chosen to mimic assignment in + Rust and key-value mappings in attributes. Alternatives include: + - `cfg_alias(name, predicate)`, which is more similar to + `cfg_attr(predicate, attributes)`. - It may be possible to have `#[cfg_alias(...)]` work as an outer macro and only apply to a specific scope. This likely is not worth the complexity. From a16418977f132204ab548ed0dca43ec148a86765 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 23 Apr 2025 21:18:11 -0400 Subject: [PATCH 4/4] Discuss optionally allowing `cfg_alias` at module scope --- text/3804-cfg-alias.md | 63 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/text/3804-cfg-alias.md b/text/3804-cfg-alias.md index a36354cfda6..92f8a750f0b 100644 --- a/text/3804-cfg-alias.md +++ b/text/3804-cfg-alias.md @@ -97,8 +97,13 @@ CfgAliasAttribute: cfg_alias(IDENTIFIER `=` ConfigurationPredicate) ``` -The identifier is added to the `cfg` namespace. It must not conflict with any -builtin configuration names, or with those passed via `--cfg` or `--check-cfg`. [^check-cfg] +The identifier is added to the `cfg` namespace. It must not conflict with: + +- Any builtin configuration names +- Any configuration passed via `--cfg` +- Any configuration passed with `--check-cfg`, since this indicates a possible + but omitted `--cfg` option +- Other aliases that are in scope Once defined, the alias can be used as a regular predicate. @@ -115,18 +120,54 @@ will emit an unknown configuration lint: #![cfg_alias(some_alias = true)] ``` -RFC Question: +_RFC question: "usable only after definition" is mentioned here to retain the +ability to parse attributes in order, rather than going back and updating +earlier attributes that may use the alias. Is this a reasonable limitation to +keep?_ + +_RFC question: two ways to implement this are with (1) near-literal +substitution, or (2) checking whether the alias should be set or not at the time +it is defined. Is there any user-visible behavior that would make us need to +specify one or the other?_ + +_If we go with the first option, we should limit to a single expansion to avoid +recursing (as is done for `#define` in C)._ + +## `cfg_alias` in non-crate attributes + +`cfg_alias` may also be used as a module-level attribute rather than +crate-level: + +```rust +#[cfg_alias(foo = bar)] +mod uses_bar { + // Enabled/disabled based on `cfg(bar)` + #[cfg(foo)] + fn qux() { /* ... */ } +} + +#[cfg_alias(foo = baz)] +mod uses_baz { + // Enabled/disabled based on `cfg(baz)` + #[cfg(foo)] + fn qux() { /* ... */ } +} +``` -Two ways to implement this are with (1) near-literal substitution, or (2) -checking whether the alias should be set or not at the time it is defined. Is -there any user-visible behavior that would make us need to specify one or the -other? +This has the advantage of keeping aliases in closer proximity to where they are +used; if a configuration pattern is only used within a specific module, an alias +can be added at the top of the file rather than making it crate-global. -If we go with the first option, we should limit to a single expansion to avoid -recursing (as is done for `#define` in C). +When defined at a module level, aliases are added to the configuration namespace +for everything within that module including later module-level configuration. +There is no conflict with aliases that use the same name in other modules. -[^check-cfg]: `--check-cfg` is included here because it indicates there may be a - corresponding `--cfg`. +This RFC proposes that the use of `cfg_alias` on modules _should_ be included if +possible. However, this may bring implementation complexity since, to the RFC +author's knowledge, the rustc configuration system is not designed to allow +scoped configuration. If implementation of module-level aliases turns out to be +nontrivial, this portion of the feature may be deferred or dropped before +stabilization. # Drawbacks