|
| 1 | +- Feature Name: `cfg_alias` |
| 2 | +- Start Date: 2025-04-23 |
| 3 | +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) |
| 4 | +- Rust Issue: |
| 5 | + [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) |
| 6 | + |
| 7 | +# Summary |
| 8 | + |
| 9 | +[summary]: #summary |
| 10 | + |
| 11 | +This RFC introduces a way to name configuration predicates for easy reuse |
| 12 | +throughout a crate. |
| 13 | + |
| 14 | +```rust |
| 15 | +#![cfg_alias( |
| 16 | + x86_linux, |
| 17 | + all(any(target_arch = "x86", target_arch = "x86_64"), target_os = "linux") |
| 18 | +)] |
| 19 | + |
| 20 | +#[cfg(x86_linux)] |
| 21 | +fn foo() { /* ... */ } |
| 22 | + |
| 23 | +#[cfg(not(x86_linux))] |
| 24 | +fn foo() { /* ... */ } |
| 25 | +``` |
| 26 | + |
| 27 | +# Motivation |
| 28 | + |
| 29 | +[motivation]: #motivation |
| 30 | + |
| 31 | +It is very common that the same `#[cfg(...)]` options need to be repeated in |
| 32 | +multiple places. Often this is because a `cfg(...)` needs to be matched with a |
| 33 | +`cfg(not(...))`, or because code cannot easily be reorganized to group all code |
| 34 | +for a specific `cfg` into a module. The solution is usually to copy a `#[cfg]` |
| 35 | +group around, which is error-prone and noisy. |
| 36 | + |
| 37 | +Adding aliases to config predicates reduces the amount of code that needs to be |
| 38 | +duplicated, and giving it a name provides an easy way to show what a group of |
| 39 | +configuration is intended to represent. |
| 40 | + |
| 41 | +Something to this effect can be done using build scripts. This requires reading |
| 42 | +various Cargo environment variables and potentially doing string manipulation |
| 43 | +(for splitting target features), so it is often inconvenient enough to not be |
| 44 | +worth doing. Allowing aliases to be defined within the crate and with the same |
| 45 | +syntax as the `cfg` itself makes this much easier. |
| 46 | + |
| 47 | +# Guide-level explanation |
| 48 | + |
| 49 | +[guide-level-explanation]: #guide-level-explanation |
| 50 | + |
| 51 | +There is a new crate-level attribute that takes a name and a `cfg` predicate: |
| 52 | + |
| 53 | +```rust |
| 54 | +#![cfg_alias(some_alias, predicate)] |
| 55 | +``` |
| 56 | + |
| 57 | +`predicate` can be anything that usually works within `#[cfg(...)]`, including |
| 58 | +`all`, `any`, and `not`. |
| 59 | + |
| 60 | +Once an alias is defined, `name` can be used as if it had been passed via |
| 61 | +`--cfg`: |
| 62 | + |
| 63 | +```rust |
| 64 | +#[cfg(some_alias)] |
| 65 | +struct Foo { /* ... */ } |
| 66 | + |
| 67 | +#[cfg(not(some_alias))] |
| 68 | +struct Foo { /* ... */ } |
| 69 | + |
| 70 | +#[cfg(all(some_alias, target_os = "linux"))] |
| 71 | +fn bar() { /* ... */ } |
| 72 | +``` |
| 73 | + |
| 74 | +# Reference-level explanation |
| 75 | + |
| 76 | +[reference-level-explanation]: #reference-level-explanation |
| 77 | + |
| 78 | +The new crate-level attribute is introduced: |
| 79 | + |
| 80 | +```text |
| 81 | +CfgAliasAttribute: |
| 82 | + cfg_alias(IDENTIFIER, ConfigurationPredicate) |
| 83 | +``` |
| 84 | + |
| 85 | +The identifier is added to the `cfg` namespace. It must not conflict with any |
| 86 | +builtin configuration names, or with those passed via `--cfg`. |
| 87 | + |
| 88 | +Once defined, the alias can be used as a regular predicate. |
| 89 | + |
| 90 | +The alias is only usable after it has been defined. For example, the following |
| 91 | +will emit an unknown configuration lint: |
| 92 | + |
| 93 | +```rust |
| 94 | +#![cfg_attr(some_alias, some_attribute)] |
| 95 | +// warning: unexpected_cfgs |
| 96 | +// |
| 97 | +// The lint could mention that `some_alias` was found in the |
| 98 | +// crate but is not available here. |
| 99 | + |
| 100 | +#![cfg_alias(some_alias, true)] |
| 101 | +``` |
| 102 | + |
| 103 | +RFC Question: |
| 104 | + |
| 105 | +Two ways to implement this are with (1) near-literal substitution, or (2) |
| 106 | +checking whether the alias should be set or not at the time it is defined. Is |
| 107 | +there any user-visible behavior that would make us need to specify one or the |
| 108 | +other? |
| 109 | + |
| 110 | +If we go with the first option, we should limit to a single expansion to avoid |
| 111 | +recursing (as is done for `#define` in C). |
| 112 | + |
| 113 | +# Drawbacks |
| 114 | + |
| 115 | +[drawbacks]: #drawbacks |
| 116 | + |
| 117 | +- This does not support more general attribute aliases, such as |
| 118 | + `#![alias(foo = derive(Clone, Copy, Debug, Default)`. This seems better suited |
| 119 | + for something like `declarative_attribute_macros` in [RFC3697]. |
| 120 | + |
| 121 | +[RFC3697]: https://github.com/rust-lang/rfcs/pull/3697 |
| 122 | + |
| 123 | +# Rationale and alternatives |
| 124 | + |
| 125 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 126 | + |
| 127 | +- The syntax `cfg_alias(name, predicate)` was chosen for similarity with |
| 128 | + `cfg_attr(predicate, attributes)`. Alternatives include: |
| 129 | + - `cfg_alias(name = predicate)` |
| 130 | +- It may be possible to have `#[cfg_alias(...)]` work as an outer macro and only |
| 131 | + apply to a specific scope. This likely is not worth the complexity. |
| 132 | + |
| 133 | +# Prior art |
| 134 | + |
| 135 | +[prior-art]: #prior-art |
| 136 | + |
| 137 | +In C it is possible to modify the define map in source: |
| 138 | + |
| 139 | +```c |
| 140 | +# if (defined(__x86_64__) || defined(__i386__)) && defined(__SSE2__) |
| 141 | +#define X86_SSE2 |
| 142 | +#endif |
| 143 | + |
| 144 | +#ifdef X86_SSE2 |
| 145 | +// ... |
| 146 | +#endif |
| 147 | +``` |
| 148 | + |
| 149 | +# Unresolved questions |
| 150 | + |
| 151 | +[unresolved-questions]: #unresolved-questions |
| 152 | + |
| 153 | +Questions to resolve before this RFC could merge: |
| 154 | + |
| 155 | +- Which syntax should be used? |
| 156 | +- Substitution vs. evaluation at define time (the question under the |
| 157 | + reference-level explanation) |
| 158 | + |
| 159 | +# Future possibilities |
| 160 | + |
| 161 | +[future-possibilities]: #future-possibilities |
| 162 | + |
| 163 | +- A `--cfg-alias` CLI option would provide a way for Cargo to interact with this |
| 164 | + feature, such as defining config aliases in the workspace `Cargo.toml` for |
| 165 | + reuse in multiple crates. |
0 commit comments