- Feature Name: cfg_attr_multi
- Start Date: 2018-09-10
- RFC PR: rust-lang/rfcs#2539
- Rust Issue: rust-lang/rust#54881
Summary
Change cfg_attr to allow multiple attributes after the configuration
predicate, instead of just one. When the configuration predicate is true,
replace the attribute with all following attributes.
Motivation
Simply put, ergonomics and intent. When you have multiple attributes you configure away behind the same predicate today, you need to duplicate the entire predicate. And then when you read code that does this, you have to check the entire predicates with each other to make sure they're the same. By allowing multiple attributes it removes that duplication and shows explicitly that the author wanted those attributes configured behind the same predicate.
Guide-level explanation
The cfg_attr attribute takes a configuration predicate and then a list of
attributes that will be in effect when the predicate is true.
For an example of multiple attributes, say we want to have two attribute macros
(sparkles and crackles), but only when feature = "magic" is enabled. We
can write this as:
#[cfg_attr(feature = "magic", sparkles, crackles)]
fn bewitched() {}When the feature flag is enabled, it expands to:
#[sparkles]
#[crackles]
fn bewitche() {}The list of attributes may be empty, but will warn if the actual source code contains an empty list.
Reference-level explanation
The next section replaces what's in the Conditional Compilation Chapter for the
cfg_attr attribute. It explains both current and new behavior, mainly because
the current reference material needs improvement.
cfg_attr Attribute
The cfg_attr attribute conditionally includes attributes based on a
configuration predicate. 
It is written as cfg_attr followed by (, a comma separated metaitem
sequence, and then ) The metaitem sequence contains one or more metaitems.
The first is a conditional predicate. The rest are metaitems that are also
attributes. Trailing commas after attributes are permitted. The following list
are all allowed:
- cfg_attr(predicate, attr)
- cfg_attr(predicate, attr_1, attr_2)
- cfg_attr(predicate, attr,)
- cfg_attr(predicate, attr_1, attr_2,)
- cfg_attr(predicate,)
Note:
cfg_attr(predicate)is not allowed. That comma is semantically distinct from the commas following attributes, so we require it.
When the configuration predicate is true, this attribute expands out to be an
attribute for each attribute metaitem. For example, the following module will
either be found at linux.rs or windows.rs based on the target.
#[cfg_attr(linux, path = "linux.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod os;For an example of multiple attributes, say we want to have two attribute macros,
but only when feature = "magic" is enabled. We can write this as:
#[cfg_attr(feature = "magic", sparkles, crackles)]
fn bewitched() {}When the feature flag is enabled, the attribute expands to:
#[sparkles]
#[crackles]
fn bewitche() {}Note: The cfg_attr can expand to another cfg_attr. For example,
#[cfg_attr(linux, cfg_attr(feature = "multithreaded", some_other_attribute))
is valid. This example would be equivalent to
#[cfg_attr(all(linux, feaure ="multithreaded"), some_other_attribute)].
Warning When Zero Attributes
This RFC allows #[cfg_attr(predicate,)]. This is so that macros can generate
it. Having it in the source text emits an unused_attributes warning.
Attribute Syntax Opportunity Cost
This would be the first place attributes would be allowed in a comma-separated list. As such, it adds a restriction that attributes cannot have a non-delimited comma.
Today, an attribute can look like:
- name,
- name(`TokenStream`)
- name = `TokenTree`
where TokenStream is a sequence of tokens that only has the restriction that
delimiters match and TokenTree is a single identifier, literal, punctuation
mark, or a delimited TokenStream.
With this RFC accepted, the following cannot ever be parsed as attributes:
- name, option
- name = some, options
Arguably, we could allow (name, option), but we shouldn't.
This restriction is also useful if we want to put multiple attributes in a
single #[] container, which has been suggested, but this RFC will not tackle.
Drawbacks
It's another thing that has to be learned. Though even there, it's just learning that the attribute takes 1+, and not just 1 attribute.
It restricts the future allowable syntaxes for attributes.
Rationale and alternatives
We could require that multiple attributes must be within in a delimiter to make
it so that it's always two arguments at the top level. E.g.,
#[cfg_attr(predicate, [attr, attr])]. While this could increase clarity, it
mostly seems like it would just add noise. In the multiline case, it already
reads pretty clear with the predicate on the first line and each attribute
indented.
The default alternative of not doing this is a possibility. It would just mean that conditionally including attributes is slightly less ergonomic than it could be.
We could change attribute container syntax to allow multiple attributes and then
state that cfg_attr takes the attribute container syntax without the #[]
part. While this could be a good final state, it's a more ambitious change that
has more drawbacks. There are legitimate reasons we'd want cfg_attr to take
multiple attributes but not the attribute container. As such, I would like to
go with the conservative change first.
The original draft of this RFC only allowed one or more attributes and did not allow the trailing comma. Because it helps macros and fits the rest of the language, it now allows those.
Prior art
I cannot think of any prior art specifically, but changing something from taking one of something to one or more of something is pretty common.
Unresolved questions
None.