My main thought here is that it’s weird to special case “a cfg that is a combination of other cfgs” and “a derive that is a combination of other derives”. Shouldn’t we go up a level and say “an attribute that’s a combination of other attributes”? That is: #![ let my_attrib = #[cfg(foo)] #[derive(Clone)] ]. (not actually proposing this.)
I was actually going to go the same route of suggesting this be implemented via macros as well. Today we can already do it this way:
but what I would like to see is the ability to put macros in attribute position, like so (though this may not work in Rust’s actual AST order):
macro_rules! my_attrib { // or `macro_rules! #[my_attrib]`
() => ( #[cfg(foo)] #[derive(Clone)] )
}
#[my_attrib!] struct A; // again, arbitrary syntax
// tiny change to today's macro syntax - just adds #[name!] in addition to name!()/name![]/name!{}
or the slightly simpler but harder to read:
macro_rules! my_attrib {
() => ( #[cfg(foo)] #[derive(Clone)] )
}
my_attrib![] struct A;
// no change to today's macro syntax, only in where macros can be placed
Another, possibly more elegant solution, would be to allow macros like #[my_attrib!] tt; which are equivalent to my_attrib!(tt); (i.e. it takes the immediately following token tree and transforms it into another token tree). Perhaps it’s too general (just “another way” to do the thing in the first example), but it seems nice and it enables a slightly nicer syntax (for the first example, and many other current uses of macros):
It’s true that #![alias(foobar = cfg(...)] already doesn’t work with the current attribute syntax, but it is not too far different from the current one.
#![alias cfg(…) = cfg(…)] is non-trivially different in my opinion and it feels strange since everywhere else we use () to capture a group. It could just be a gut reaction and I’ll definitely add it as an alternative when I post it as an RFC though.
@kennytm
I think that when macros allowed in attributes, this in theory could work but it feels like a hacky way of allowing attribute aliases.
I subscribe fairly heavily to the concept of making easy thing easy and hard things possible. Macros solve hard problems which make them very useful but I find them to be unintuitive at first glance. I think the problem we are trying to solve is common enough, and “easy” enough that the solution should be easy at the moment of creation (making the alias) and at the moment of use (using the alias). I think that relying on attribute macros to solve this will fail on both ends.
When I compare these two pieces of code:
macro bar() {
all(complicated, stuff, not(easy, to, remember))
}
#[cfg(bar!())]
mod foo
#[cfg(not(bar!()))]
mod baz
#![alias(bar = cfg(all(complicated, stuff, not(easy, to, remember)))]
#[cfg(bar)]
mod foo
#[cfg(not(bar))]
mod foo
I can’t help but feel like the second one is more intuitive and ergonomic. In the first example I have to both know and care that bar!() is a macro instead of simply using it as any other cfg attribute. When the alias is created, there is nothing in the alias that says this belongs in a cfg attribute, so it is non-obvious what the purpose of it is.
I think if the user needs something more complex out of their attribute alias, then their case is no longer the easy one and then they can make a macro for their use case once attribute macros are introduced in Rust.
@kainino
I agree that an attribute that is a combination of other attributes is ideal. However, I also think that if we allowed aliasing only cfg attributes at first, without putting us into a corner, we can start with a small feature that adds user-value in a non-breaking way. Please read my comment to @kennytm for my issue with solving this using macros.