- Feature Name:
cfg_alias - Start Date: 2025-04-23
- RFC PR: rust-lang/rfcs#3804
- Rust Issue: rust-lang/rust#0000
This RFC introduces a way to name configuration predicates for easy reuse throughout a crate.
#![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() { /* ... */ }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:
#![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() { /* ... */ }There is a new crate-level attribute that takes a name and a cfg predicate:
#![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:
#[cfg(some_alias)]
struct Foo { /* ... */ }
#[cfg(not(some_alias))]
struct Foo { /* ... */ }
#[cfg(all(some_alias, target_os = "linux"))]
fn bar() { /* ... */ }The new crate-level attribute is introduced:
CfgAliasAttribute:
cfg_alias(IDENTIFIER `=` ConfigurationPredicate)
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--cfgoption - Other aliases that are in scope
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:
#![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?
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 may also be used as a module-level attribute rather than
crate-level:
#[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() { /* ... */ }
}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.
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.
- This does not support more general attribute aliases, such as
#![alias(foo = derive(Clone, Copy, Debug, Default). This seems better suited for something likedeclarative_attribute_macrosin RFC3697.
- 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 tocfg_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.
In C it is possible to modify the define map in source:
# if (defined(__x86_64__) || defined(__i386__)) && defined(__SSE2__)
#define X86_SSE2
#endif
#ifdef X86_SSE2
// ...
#endifQuestions 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)
- A
--cfg-aliasCLI option would provide a way for Cargo to interact with this feature, such as defining config aliases in the workspaceCargo.tomlfor reuse in multiple crates.