Summary
Allow expression macros in attributes. For example:
#[path = concat!(env!("OUT_DIR"), "/hello.rs")]
mod foo;
Motivation
Users sometimes attempt to use macros inside of attributes such as #[path]
, #[export_name]
, and #[link_section]
. These are commonly used in code generation where being able to programmatically control their values is useful. Additionally, #[doc = include_str!("file")]
would allow large explanatory texts to be moved outside of the current source file.
Currently, expression macros inside attributes cause parsing errors when included directly (rust-lang/rust#18849), and attempts to get around this restriction using macro indirection causes ICEs (rust-lang/rust#42164).
This RFC proposes to allow expression macros inside attributes to allow these use cases.
Guide-level explanation
Built-In Attributes
The previous use cases are probably mostly encountered by experienced Rust developers, who might be more surprised that macros don’t work here.
One consideration is making sure error messages are informative, for instance we should expect similar errors to when existing macros receive unexpected tokens. For example, this:
fn main() {
let s = "hello!";
println!(s);
}
Gives the following error:
error: expected a literal
--> bin.rs
|
3 | println!(s, 5);
| ^
Which inspires the following:
#[path = include!("file")]
mod foo;
Should error with:
error: expected a literal after macro expansion
--> bin.rs
|
1 | #[path = include!("file")]
| ^^^^^^^^^^^^^^^^
Procedural Macros and Custom Attributes
Procedural macros can specify custom attributes which are passed to the macro before macro expansion. We should update the proc macro reference with guidelines on expanding macros in their input.
Reference-level explanation
A relatively small change is sufficient to address the #[export_name]
and #[link_section]
cases outlined above. Specifically:
- Allow key-value attributes to have a macro as their value node, updating the parser and AST accordingly.
- After macro expansion, confirm that the macro nodes are expanded into literal expressions.
Unfortunately, #[path]
and #[cfg]
are interpreted too early by the parser for this to work (see unresolved questions below).
Drawbacks
- Expanding macros into one part of attributes (key-value values) but not others (attribute lists, attribute identifiers themselves) may be confusing or surprising.
- Cargo currently ignores environment variables when determining when to recompile parts of a project. This may make
env!
behave surprisingly when used as above. We can mitigate this by either documenting the issue in the reference page for attributes, or updating Cargo to take a snapshot of environment variables during builds.
Alternatives
For some use cases there are existing solutions in the crate ecosystem using proc macros (see here), however this is rather heavyweight.
There is always the option of using build.rs
to generate files with the relevant contents, but this seems like a code smell.
Unresolved questions
- Do we extend the legal positions of expression macros to include list items (e.g.
#[name(macro!(...))]
)? - Are ident macros stable enough to be supported, or is restricting to expression macros sufficient?
- The implementation of
#[path = <value>]
tries to use<value>
as a string literal long before macro expansion takes place. Can we delay parsing paths until after that? This seems hard to do, since#[path]
is used to find files to parse. - Similarly, the feature-gated
#[cfg(name = <value>)]
syntax also tries to use<value>
before macro expansion. How do we handle this?