Skip to content

Commit 304fefc

Browse files
committed
Introduce an RFC for config aliases
1 parent e4bff82 commit 304fefc

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

text/0000-cfg-alias.md

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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

Comments
 (0)