In this lesson, you will learn how to reduce compilation bottlenecks by minimizing the depth and complexity of your macro expansions.
The Cost of Expansion
Every time you invoke a macro, the Rust compiler must parse the input tokens, expand them into new code, and perform recursive expansions if your macro calls others. When you have a high volume of calls or deeply nested expansions, the compiler’s work grows non-linearly. To keep compilation times within acceptable limits, we must treat macro expansion as a "pay-as-you-go" cost.
Minimize Recursive Depth
The most common performance killer is excessive recursion. If a macro calls itself to process a list of items, each step adds overhead to the macro parser. Instead of deeply nested recursive structures, aim for flat or iterative expansions.
Consider a macro that generates a lookup table. Avoid this:
macro_rules! recursive_table {
($head:expr, $($tail:expr),+) => {
// This forces N expansions for N items
[ $head, recursive_table!($($tail),+) ]
};
($last:expr) => { [$last] };
}
Instead, use repetition indicators (lesson 12) to expand in a single pass. The compiler handles the repetition internally much faster than the macro expansion engine handles recursion:
macro_rules! flat_table {
($($x:expr),*) => {
// Single expansion pass: O(1) in the macro engine
[$($x),*]
};
}
Reduce Token Bloat
Each token you generate must be tracked, checked for hygiene, and type-checked later. To optimize:
- Avoid unnecessary
macro_rules!re-definitions: If a helper macro is needed, define it once in a module rather than creating it inside the body of another macro, which forces the compiler to re-process the helper's definition on every invocation. - Be specific with fragments: Using
:tt(token tree) captures everything and forces the compiler to parse more than necessary. Use precise fragment specifiers like:expror:ident(lesson 10) to limit the compiler’s search space. - Limit internal intermediate code: Generate only what is strictly necessary. If you can move logic from a macro into a
const fn, do so;const fnis cached and optimized by the compiler, whereas macros are expanded and re-parsed repeatedly.
Key Takeaway: Optimize macro performance by favoring internal repetition indicators over recursive expansion and moving logic into const fn to reduce the total volume of generated code.