# \[Pre-RFC\] Macros in Attributes

**URL:** <https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767>\
**Category:** language design\
**Created:** [August 16, 2017, 5:16am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767 "2017-08-16T05:16:24Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![pierzchalski](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pierzchalski/32/2521_2.png) [@pierzchalski](https://internals.rust-lang.org/u/pierzchalski)\
**Post date:** [August 16, 2017, 5:16am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/1 "2017-08-16T05:16:24Z")

</div>

# Summary

Allow expression macros in attributes. For example:

```rust
#[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](https://github.com/rust-lang/rust/issues/18849)), and attempts to get around this restriction using macro indirection causes ICEs ([rust-lang/rust#42164](https://github.com/rust-lang/rust/issues/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:

```rust
fn main() {
    let s = "hello!";
    println!(s);
}

```

Gives the following error:

```rust
error: expected a literal
 --> bin.rs
  |
3 | println!(s, 5);
  | ^

```

Which inspires the following:

```rust
#[path = include!("file")]
mod foo;

```

Should error with:

```rust
error: expected a literal after macro expansion
 --> bin.rs
  |
1 | #[path = include!("file")]
  | ^^^^^^^^^^^^^^^^

```

### Procedural Macros and Custom Attributes

Procedural macros can specify [custom attributes](https://doc.rust-lang.org/nightly/book/first-edition/procedural-macros.html#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](#motivation). 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](#unresolved-questions)).

# 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](https://github.com/rust-lang/rust/issues/18810#issuecomment-62450616)), 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?

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [August 16, 2017, 11:43am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/2 "2017-08-16T11:43:04Z")

</div>

For modules this may work:

```rust
mod foo {
   include!(concat!(env!("OUT_DIR"), "/hello.rs"))
}

```

If you’re using code-generation, could you make names configurable as part of your customo code generation process?

Macros can match attributes (and rewrite them too, I think), so something like this may be possible:

```rust
prefix_export! { name, {
   #[export_name="foo"] …
}}

```

---

<div class="post-metadata">

**Author:** ![durka](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/durka/32/1097_2.png) [@durka](https://internals.rust-lang.org/u/durka)\
**Post date:** [August 16, 2017, 4:43pm UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/3 "2017-08-16T16:43:39Z")

</div>

I think this is a great idea. Since anything to do with modules is controversial these days, something like `#[export_name = concat!("foo", $bar)]` might be a better motivating example.

@kornel: Refuting specific examples of a general facility is always hazardous, but anyway both of the solutions you point out are not complete: `include!` [only works for a single statement](https://github.com/rust-lang/rust/issues/35560), and attempting to construct `#[export_name]` via a macro [ICEs as already mentioned](https://github.com/rust-lang/rust/issues/42164).

---

<div class="post-metadata">

**Author:** ![pierzchalski](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pierzchalski/32/2521_2.png) [@pierzchalski](https://internals.rust-lang.org/u/pierzchalski)\
**Post date:** [August 17, 2017, 1:29am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/4 "2017-08-17T01:29:34Z")

</div>

Thankfully, I don’t think the module system changes are going to sweep in and invalidate the above motivating `#[path]` example for at least a few months, although you’re right that the timing isn’t great.

It’s also the only use I could find which _doesn’t_ involve defining a macro (which would be a bit of a distraction), although I guess it’s not totally unreasonable to have `#[export_name = env!("CONFIG_VAR")]`.

---

<div class="post-metadata">

**Author:** ![fitzgen](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/fitzgen/32/3625_2.png) [@fitzgen](https://internals.rust-lang.org/u/fitzgen)\
**Post date:** [August 17, 2017, 1:31am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/5 "2017-08-17T01:31:05Z")

</div>

I’ve always wanted to be able to do:

```rust
#![doc=include!("README.md")]

```

---

<div class="post-metadata">

**Author:** ![nrc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nrc/32/126_2.png) [@nrc](https://internals.rust-lang.org/u/nrc)\
**Post date:** [August 17, 2017, 2:57am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/6 "2017-08-17T02:57:33Z")

</div>

In general attributes are themselves macros which get passed their parameters pre-expansion. That means that it is up to the attribute what it does with any macros it finds, and if it chooses to expand them, in what context. The libraries for procedural macros should make this easy and I hope that most attributes do this (and we should probably have guidelines for macros which recommend doing it). That leaves the built-in attributes. It is probably worth examining these on a case by case basis, and where the arguments are treated like an expression, expand macros there.

---

<div class="post-metadata">

**Author:** ![pierzchalski](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pierzchalski/32/2521_2.png) [@pierzchalski](https://internals.rust-lang.org/u/pierzchalski)\
**Post date:** [August 17, 2017, 4:08am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/7 "2017-08-17T04:08:59Z")

</div>

@nrc I’m probably showing off my unfamiliarity with this area, but what do you mean by ‘general’ versus ‘built-in’ attributes? I don’t see any reference to them in [the reference](https://doc.rust-lang.org/nightly/reference/attributes.html) - are you referring to [proc macro attributes](https://doc.rust-lang.org/nightly/book/first-edition/procedural-macros.html#custom-attributes)?

As far as I can tell, [`concat!`](https://github.com/rust-lang/rust/blob/be0f77dc8a12c40c184a47e5d51ec9001c4bd360/src/libsyntax_ext/concat.rs#L20) and friends are implemented as a syntax extension in `libsyntax_ext`, run after parsing but long before some of the built-in attributes get used: it seems [`#[link_section]`](https://github.com/rust-lang/rust/blob/be0f77dc8a12c40c184a47e5d51ec9001c4bd360/src/librustc_trans/base.rs#L642) and [`#[export_name]`](https://github.com/rust-lang/rust/blob/be0f77dc8a12c40c184a47e5d51ec9001c4bd360/src/librustc_trans/back/symbol_names.rs#L248) are evaluated in `librustc_trans`, but [`#[path]`](https://github.com/rust-lang/rust/blob/be0f77dc8a12c40c184a47e5d51ec9001c4bd360/src/libsyntax/parse/parser.rs#L5357) gets interpreted in `libsyntax` itself. Is there a way to bring the `concat!` evaluation ‘forward’? I can’t quite get my head around what `libsyntax::ext::expand` is doing…

Looking in `libsyntax::ast`, the `"value"` in `#[name = "value"]` gets parsed into a `MetaItemKind::NameValue(Lit)`. If we let `NameValue` hold either a `Lit` or `Mac` (macro call) and update `fold`, is that enough for proc macro attributes, `#[link_section]`, and `#[export_name]`?

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [August 17, 2017, 3:06pm UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/8 "2017-08-17T15:06:40Z")

</div>

> [@pierzchalski](#):
>
> #[export\_name = env!("CONFIG\_VAR")].

Beware that at least currently Cargo is unaware of user env vars. If you change the var, the relevant parts of the project will not be recompiled, and you'll get a mixed result.

---

<div class="post-metadata">

**Author:** ![pierzchalski](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pierzchalski/32/2521_2.png) [@pierzchalski](https://internals.rust-lang.org/u/pierzchalski)\
**Post date:** [August 18, 2017, 4:29am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/9 "2017-08-18T04:29:45Z")

</div>

@fitzgen @kornel Motivation and discussion updated, thanks!

I’m looking at how `#[cfg]` and `#[path]` get interpreted, and it’s looking less and less easy to allow macro expansion inside them (or at least, expansion with macro `$variables`), since they can (for instance) change the available macro definitions. Anyone have any ideas?

---

<div class="post-metadata">

**Author:** ![pierzchalski](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pierzchalski/32/2521_2.png) [@pierzchalski](https://internals.rust-lang.org/u/pierzchalski)\
**Post date:** [January 29, 2018, 11:36pm UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/10 "2018-01-29T23:36:55Z")

</div>

For anyone still interested, after discussion with @nrc this turned into an (in-review) RFC for a macro expansion API, which you can find [here](https://github.com/pierzchalski/rfcs/blob/macros-in-attributes/text/0000-proc-macro-expansion-api.md). I’d love to hear any thoughts on what’s missing or how to improve what’s there before I make the PR!

Edit: RFC now [live](https://github.com/rust-lang/rfcs/pull/2320).

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [March 25, 2019, 8:28am UTC](https://internals.rust-lang.org/t/pre-rfc-macros-in-attributes/5767/11 "2019-03-25T08:28:54Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
