Current macro expansion order
⚓ Rust 📅 2025-12-31 👤 surdeus 👁️ 1I haven't been able to find a good, one place resource to find the actual macro expansion order, so I'll try to summarize what I think I have found the order is, alongside the questions I have found no answer to. Any mistakes on my part please do correct me, though.
- As per this PR, the derive macro is treated like any other attribute macro, and so should be the case for doc and the prior inert macros, but that I'm not sure about.
- Macro order among different items is NOT determined, consistent or reliable (since they are executed separately and multithreaded-ly).
- All macros are, in general, resolved from outwards in. That is, the outer macro is fed the entirety of the token stream inside it, finishes, then the next proceeds.
- This applies to all macros, with exceptions 4 and 5, respectively.
- e.g.
(Order, if none start stripping other attributes: attr1, derive, attr2, attr3)#[attr1]#[derive(Foo)] #[attr2] struct Bar { field1: i64, #[attr3] field2: bool, }
In this example,attr1is passed the entirety of the rest of the token stream (even the attribute on the field). Then,attr1resolves and outputs a token stream (if it doesn't fail). Suppose it doesn't, and the output stream is the same as the input (exactly the example withoutattr1), then it is reevaluated and the outer macro is now#[derive(Trait)], which is given the rest of the token stream, is evaluated, and so on. - e.g.
(Order, if no macro removes other macros: macro1, 2 then 3)macro1!(macro2!(macro3!()))
Again, macro1 is given the entirety of the rest of the token stream, is evaluated and returns a token stream, which is then evaluated, and so on.
deriveis special: Since derive macros can have helpers,cfgandcfg_attron fields are expanded before derive evaluation and stripped afterwards.- I do not know when exactly they are stripped, and if the following attribute macros still receive the stripped attributes.
- Or if the
cfgs are then properly evaluated for the next attribute macros. - Or if the
cfgs are all expanded and not only the needed for the helpers.
format_argsis special: And any macro that uses it under the hood, and probably others in the standard library which I do not have a list for, evaluate the inner macros before it. It does make sense from a user standpoint, but it does break the ordering.cfgandcfg_attraren't stripped at all before non derive macros. That is, attribute macros don't see fake inner attribute macros expanded, they see it fully. It's not thecfgandcfg_attrwhich are special (I argue they should), it's thederive.- There is a workaround implemented in bon and gecs as per this comment idea.
(edit 1: Added workaround to 6)
(edit 2: Changed category to help since I do need help making sure this is correct but I also wished for a place with all this info condensed to begin with)
1 post - 1 participant
🏷️ Rust_feed