-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: naming groups of configuration with cfg_alias
#3804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
304fefc
780ccd7
47d8ed1
a164189
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,225 @@ | ||||||||||
- Feature Name: `cfg_alias` | ||||||||||
- Start Date: 2025-04-23 | ||||||||||
- 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) | ||||||||||
|
||||||||||
# 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. | ||||||||||
|
||||||||||
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)] | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In #3804 (comment) I had meant more that you could use e.g.: #[cfg(todo)]
pub fn uses_some_api_that_isnt_finished() {
api::cool_function_that_doesnt_exist_yet();
} later, once that api is implemented, you can just delete the |
||||||||||
fn to_be_tested() { /* ... */ } | ||||||||||
|
||||||||||
|
||||||||||
#[test] | ||||||||||
#[cfg(todo)] | ||||||||||
fn test_to_be_tested() { /* ... */ } | ||||||||||
``` | ||||||||||
|
||||||||||
# 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`. | ||||||||||
Comment on lines
+72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd probably drop ", including..." here as it makes it more rather than less ambiguous. Alternatively, it'd be OK to say e.g. "including (but not limited to) combining operators such as |
||||||||||
|
||||||||||
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() { /* ... */ } | ||||||||||
``` | ||||||||||
Comment on lines
+75
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add an example of an alias being used within an alias to make clear that that should work. The example in https://crates.io/crates/cfg_aliases provides a good motivating case. Might also be good to pull ideas from that readme for the Summary section |
||||||||||
|
||||||||||
# Reference-level explanation | ||||||||||
|
||||||||||
[reference-level-explanation]: #reference-level-explanation | ||||||||||
|
||||||||||
The new crate-level attribute is introduced: | ||||||||||
|
||||||||||
```text | ||||||||||
CfgAliasAttribute: | ||||||||||
cfg_alias(IDENTIFIER `=` ConfigurationPredicate) | ||||||||||
``` | ||||||||||
Comment on lines
+95
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have a look at the grammar syntax in the Reference. Probably best to just use that. |
||||||||||
|
||||||||||
The identifier is added to the `cfg` namespace. It must not conflict with: | ||||||||||
|
||||||||||
- Any builtin configuration names | ||||||||||
- Any configuration passed via `--cfg` | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to clarify, if we passed |
||||||||||
- Any configuration passed with `--check-cfg`, since this indicates a possible | ||||||||||
but omitted `--cfg` option | ||||||||||
- Other aliases that are in scope | ||||||||||
Comment on lines
+100
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this make the compatibility hazard worse than today? Code can work perfectly fine but will then break if a new builtin is added or somewhere else in the dependency tree defines a new cfg where the end-user would do Is that acceptable? Or can we mitigate this somehow? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had almost left this same comment, nearly word for word, last week, and then I had thought to myself, "well, that's kind of already the case, isn't it? Someone could be doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Someone else had brought the issue up and I also discarded it but I wasn't aware of the erroring part of this at the time. I feel I had come up with a case for why I suspect that I wonder how https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618 might change things. Unfortunately, that is too undeveloped to really say. However, if we supported shadowing (#3804 (comment)) instead of erroring, it would remove any concerns over |
||||||||||
|
||||||||||
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: "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?_ | ||||||||||
Comment on lines
+123
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative, which might be more consistent with allowing this attribute more generally at a module/item level:
This would preclude using #[cfg_alias(some_alias = true)] // declare alias
#[cfg_attr(some_alias, some_attribute)] // warning: unexpected_cfgs
mod foo {
#[cfg_attr(some_alias, some_attribute)] // works
mod bar {}
}
#[cfg_attr(some_alias, some_attribute)] // warning: unexpected_cfgs
mod xyz {} |
||||||||||
|
||||||||||
_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: | ||||||||||
Comment on lines
+138
to
+139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why module level? Once we eat the cost of making these scoped, I'm curious if there's a reason we shouldn't specify them for other scopes also. In terms of how we specify the language, it actually makes the specification simpler to have fewer rather than more exceptions. (We can always of course still incrementally stabilize if there are reasons to do so.) Of course, if we go with a different design, such as leaning into macros somehow, then we could sidestep this question. |
||||||||||
|
||||||||||
```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() { /* ... */ } | ||||||||||
} | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add some details on hygiene. For instance, what happens if you write a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||
``` | ||||||||||
|
||||||||||
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. | ||||||||||
|
||||||||||
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. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about shadowing? IMO it should be supported like we do for any other scoped variable: #[cfg_alias(foo = bar)]
mod a {
// Enabled/disabled based on `cfg(bar)`
#[cfg(foo)]
fn qux() { /* ... */ }
#[cfg_alias(foo = baz)]
mod b {
// Enabled/disabled based on `cfg(baz)`
#[cfg(foo)]
fn qux() { /* ... */ }
}
} |
||||||||||
|
||||||||||
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 | ||||||||||
|
||||||||||
[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 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inspired by the above, one alternative that comes to mind is declarative attribute macros that do the cfg matching for you. I think actually you have to declare two macros, one when the cfg you want is true and one when it is false, so that's a major drawback because it would require repeating the same clause twice. However, attribute macros (possibly in combination with this feature) would allow a crate to "export" an alias. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point, I'll mention that. One other downside is that with a specific set of config tied to an attribute macro, it wouldn't be easily possible to combine with other config in Exporting would be quite convenient at times. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point that the macro approach doesn't compose all that well. I wish there was an obvious way to support exporting these. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For a more radical design, it might be possible to treat aliases as effectively a new kind of macro; something that shares the macro namespace but only expands within It sounds borderline too complex for an otherwise pretty simple feature, but being able to do that could be a nice help if public macros expand to code that contains With that, it would almost be possible to define the builtin There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect some kind of way to export an alias. I'd be disappointed with a design that wouldn't permit it. Anyway, your comment about macros made me think of something like... // library
#[macro_export]
macro_rules! has_atomics {
() => { any(target = a, target = b, .. ) }
}
// crate
#[cfg(has_atomics!())]
fn blah(){} That has the advantage of not needing a new kind of attribute or new syntax to define an alias. It also makes it obvious when an alias is being used. Moreover this syntax implies that you can pass arguments. Let me give an example where this would be useful to me personally. The Python C api has different ABI guarantees. Take That means I define the bindings as: extern "C" {
#[cfg(any(Py_3_9, all(Py_3_12, not(Py_LIMITED_API))))]
pub fn PyObject_Vectorcall(..) -> ... This would be a lot simpler if the syntax is macro-like: macro_rules! limited {
($added_in:ident) => { all($added_in, not(Py_LIMITED_API)) }
($added_in:ident, $stable_in:ident) => { any($stable_in, all($added_in, not(Py_LIMITED_API))) }
}
#[cfg(limited!(Py_3_9, Py_3_12))]
// ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think @mejrs that this would be quite nice. I suspect it might be difficult to implement, though. Maybe what's needed is an experiment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 for that. |
||||||||||
|
||||||||||
[rationale-and-alternatives]: #rationale-and-alternatives | ||||||||||
|
||||||||||
- 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. | ||||||||||
tgross35 marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+190
to
+191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since this has now been added to the RFC, this alternative can be removed. |
||||||||||
|
||||||||||
# Prior art | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cfg_aliases crate could be mentioned. https://crates.io/crates/cfg_aliases There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I only have limited knowledge of the cfg_aliases crate implementation, but worked on the crate which the cfg_alias macro was based upon initially, and testing cfg_aliases it appears to suffer from similar limitations in it's cfg parsing. In particular it has difficulty with combinations of nested and sequential vararg cfgs such as I feel like both of these are good reasons for cargo not to defer to doing it via a crate, even if the crate is good enough to be useful for most purposes. |
||||||||||
|
||||||||||
[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 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we decide that the complexity of allowing this at a module level is too high, that is, only allowing it as a crate attribute, then I would argue that the feature would be better suited to only being accessible in Cargo / |
||||||||||
|
||||||||||
[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) | ||||||||||
Comment on lines
+216
to
+217
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
# 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. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
+223
to
+225
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I strongly suspect that having this feature in Cargo will be desired, and that having it there would solve 90% of the use-case for this feature. Implementation-wise, I also suspect that doing it would be possible and fairly simple in Cargo today by parsing the So maybe it would be valuable to implement (or at least stabilize) the ability for doing this in Cargo first, and only later consider making this an attribute in the language itself? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This gets into a weird gray area of Cargo. Defining Cargo does allow reading of cfg's through build script environment variables and through Depending on how you look at it, a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As pointed out, there is a build script to emulate cfgs: https://github.com/rust-lang/rfcs/pull/3804/files#r2059296180 With metabuild, we could go a step further in semi-native cargo support. This would allow something like [[package.build]]
dependency = "cfg_aliases"
cfg = {
wasm = 'target_arch = "wasm32"',
android = 'target_os = "android"',
surfman = 'all(unix, feature = "surfman", not(wasm))'
} (newlines in the inline table is me being hopeful that TOML 1.1 is finally unblocked) |
Uh oh!
There was an error while loading. Please reload this page.