rust-syntax-macros-declarative
The mechanics of declarative macros (macro_rules!) in Rust: matcher and transcriber syntax, every fragment specifier, repetition operators, follow-set restrictions, mixed-site hygiene, $crate, TT-munching recursion, #[macro_export] and #[macro_use] scoping, and the edition 2024 expr widening.
Cross-references: [[rust-syntax-macros-procedural]] (when to graduate to proc-macros) [[rust-syntax-edition-2024]] (edition 2024 expr widening, pat 2021 widening) [[rust-syntax-pattern-matching]] (same matcher-style mindset).
When to use this skill
- User writes a
macro_rules! my_macro { ... } and the matcher does not compile or does not match the call site
- User asks "which fragment specifier should I use for X" (ident vs expr vs tt vs path)
- User writes a repetition
$( ... )* and gets E0617 ("ambiguity") or duplicate-binding errors
- User writes
#[macro_export] and the macro fails to find its own helper items from a downstream crate
- User implements a counter / TT-muncher and hits "recursion limit reached"
- User upgrades to edition 2024 and an
expr fragment now matches _ or const { ... } where it did not before
- User asks "what is
$crate", "what is $tt", "why does my macro see my outer x and not the caller's x"
- User mixes
#[macro_use] with the 2018+ path-based import and is confused which one to use
For procedural macros (#[derive(...)], attribute macros, function-like proc-macros) see [[rust-syntax-macros-procedural]]. For edition 2024 specifics see [[rust-syntax-edition-2024]].
Quick reference: fragment specifiers
| Specifier |
Matches |
Follow-set (tokens that may follow $x:F) |
block |
a brace-delimited block expression { ... } |
any token |
expr |
an expression (edition 2024: also _ and const { ... } at top level) |
=>, ,, ; |
expr_2021 |
an expression excluding top-level _ and const { ... } |
=>, ,, ; |
ident |
an identifier or keyword (not _, not $crate) |
any token |
item |
a top-level item (fn, struct, impl, mod, ...) |
any token |
lifetime |
a 'a style lifetime token |
any token |
literal |
a literal optionally preceded by - |
any token |
meta |
the contents of an attribute (#[meta]) |
any token |
pat |
a pattern, including top-level | (since edition 2021) |
=>, ,, =, |, if, in |
pat_param |
a pattern WITHOUT top-level | |
=>, ,, =, |, if, in |
path |
a TypePath-style path (std::vec::Vec, crate::m::T) |
=>, ,, =, |, ;, :, >, >>, [, {, as, where |
stmt |
a statement WITHOUT trailing ; (the matcher does not consume the ;) |
=>, ,, ; |
tt |
a single token tree (one token, or a matched delimiter group) |
any token |
ty |
a type |
=>, ,, =, |, ;, :, >, >>, [, {, as, where |
vis |
a possibly empty visibility (pub, pub(crate), pub(in ...), or nothing) |
,, an identifier, a keyword that begins an item, (, [, { |
ALWAYS pick the most specific fragment that works. NEVER reach for tt unless you genuinely need raw token capture (TT-munching, pass-through). tt defeats most safety the macro system gives you because it accepts anything but loses semantic shape.
ALWAYS match the follow-set: writing $e:expr $name:ident is rejected because no token may follow expr other than =>, ,, or ;. Insert a literal separator (e.g. ,) between fragments to make the call site unambiguous.
Source: Rust Reference, macros-by-example
Decision tree: pick a fragment specifier
Capturing a name (variable, fn, type-name token)?
├── one identifier → ident
└── a path with :: → path
Capturing a value or computation?
├── any expression → expr (or expr_2021 if you need pre-2024 semantics)
├── a block { ... } → block
└── a statement, no `;` → stmt
Capturing a type position?
└── any type → ty
Capturing a pattern?
├── allow `A | B` → pat
└── single alternative → pat_param
Capturing an attribute body?
└── meta
Capturing literals?
└── literal
Capturing lifetimes (`'a`)?
└── lifetime
Capturing visibility (pub, pub(crate), empty)?
└── vis
Capturing an item (fn, struct, impl)?
└── item
Capturing arbitrary tokens (TT-munching, pass-through)?
└── tt (last resort)
Minimal macro_rules! syntax
macro_rules! my_macro {
( $name:ident, $value:expr ) => {
let $name = $value;
};
}
fn main() {
my_macro!(x, 1 + 2);
println!("{x}");
}
Grammar (from the Rust Reference):
MacroRulesDef := ( MacroRules ) ; // or [ MacroRules ] ; or { MacroRules }
MacroRules := MacroRule ( ; MacroRule )* ;?
MacroRule := MacroMatcher => MacroTranscriber
MacroMatcher := ( MacroMatch* ) // or [ ... ] or { ... }
MacroTranscriber:= DelimTokenTree
Rules :
- Multiple
(matcher) => { transcriber } arms are allowed; first matching arm wins.
- The matcher delimiter is independent of the call-site delimiter.
my_macro!(x), my_macro![x], and my_macro!{x} all parse the same matcher.
- Metavariables are written
$name:fragment in the matcher and $name in the transcriber.
- Literal tokens in the matcher MUST appear verbatim at the call site (commas, semicolons, keywords, punctuation).
Source: Rust Reference, macros-by-example
Repetition: $( ... )*, $( ... )+, $( ... )?
macro_rules! vec_of {
( $( $x:expr ),* $(,)? ) => {{
let mut v = Vec::new();
$( v.push($x); )*
v
}};
}
let v: Vec<i32> = vec_of!(1, 2, 3,);
Rules :
* = zero or more, + = one or more, ? = zero or one.
- A separator token (e.g.
,) goes between ) and the repetition operator: $( $x:expr ),*.
? repetition MUST NOT have a separator. $(,)? is correct (one optional comma); $( $x:expr ),? is rejected.
- The transcriber MUST mirror the same repetition shape:
$( v.push($x); )* mirrors the $( $x:expr ),* matcher.
- Multiple metavariables inside one repetition group MUST have the same length:
$( $k:ident = $v:expr ),* binds $k and $v together.
- Nested repetitions are allowed:
$( $( $x:expr ),* );*: outer ;-separated groups of inner ,-separated lists.
ALWAYS allow a trailing separator at the call site using $(,)? (or $(;)?). Users expect vec![1, 2, 3,] to parse.
Source: Rust Reference, macros-by-example
Metavariable expressions (stable Rust 1.86+)
Inside the transcriber, the following expressions read repetition state:
| Expression |
Meaning |
${count($x)} |
total number of times $x was matched (outermost) |
${count($x, depth)} |
count at a given nesting depth |
${index()} / ${index(depth)} |
current zero-based repetition index |
${len()} / ${len(depth)} |
length of the current repetition |
${ignore($x)} |
mention $x to bind it to the surrounding repetition without expanding it |
$$ |
literal $ token (escape) |
macro_rules! enumerated {
( $( $name:ident ),* $(,)? ) => {
$( println!("{} = {}", ${index()}, stringify!($name)); )*
};
}
enumerated!(alpha, beta, gamma);
// 0 = alpha 1 = beta 2 = gamma
ALWAYS use ${count(...)} instead of hand-rolled TT-munching when you only need the count. NEVER recurse for what a metavariable expression handles directly.
Source: Little Book of Rust Macros, metavariable expressions
Hygiene: mixed-site, in plain language
Macros by example have mixed-site hygiene :
- Local variables, loop labels, and block labels resolve at the macro definition site.
- All other symbols (functions, types, modules, traits, statics) resolve at the macro invocation site.
let x = 1;
fn func() { unreachable!("definition site") }
macro_rules! check {
() => {
assert_eq!(x, 1); // `x` is the definition-site x (== 1)
func(); // `func` is whatever func() exists at the call site
};
}
fn main() {
let x = 2;
fn func() { /* called by check!() */ }
check!(); // assert passes; the call-site func() runs
}
Rules :
- NEVER assume a local variable name in the caller's scope. If the macro needs a variable, declare it inside the transcriber.
- NEVER assume a type or function is in scope at the call site. Use absolute paths via
$crate::... (see next section).
- ALWAYS test the macro from a different crate AND from a different module to confirm hygiene assumptions.
Source: Rust Reference, macros-by-example § Hygiene
$crate: the absolute path to the defining crate
When a #[macro_export] macro references its own crate's items, ALWAYS use $crate::.... Plain crate::... resolves at the invocation site, not the definition site, so it breaks the moment the macro is called from another crate.
// In crate `helper`:
pub mod internals {
pub fn helper_fn() {}
}
#[macro_export]
macro_rules! call_helper {
() => { $crate::internals::helper_fn() };
}
// In a downstream crate:
use helper::call_helper;
fn main() {
call_helper!(); // expands to `helper::internals::helper_fn()` -- correct
}
Rules :
- ALWAYS write
$crate::path::to::item inside #[macro_export] macros.
$crate does NOT bypass visibility. helper_fn MUST still be pub (and reachable through pub mod).
$crate is itself a token, so ${ignore($crate)} is illegal: treat it as a path prefix only.
Source: Rust Reference, macros-by-example § $crate
Scoping: #[macro_export] vs #[macro_use] vs path imports
| Mechanism |
Scope |
When to use |
#[macro_export] on the macro |
exports the macro at the crate root |
crates that publish macros for downstream users |
use crate::path::my_macro; (2018+) |
bring an exported macro into scope by path |
the modern way to consume macros |
#[macro_use] extern crate foo; |
textual import of every exported macro of foo |
legacy 2015 edition only |
#[macro_use] mod m; |
textual import of every macro_rules! from a child module |
discouraged; prefer pub(crate) use m::name; |
ALWAYS prefer pub use crate::path::name; (edition 2018+) for both internal and external macro re-export. #[macro_use] is order-sensitive and brittle; reserve it for crates that still ship as 2015.
A macro_rules! macro without #[macro_export] is private to the module that defined it. Re-export it with pub(crate) use my_macro; or pub use my_macro; to widen its scope without exposing it at the crate root.
Source: Rust Reference, macros § Path-Based Scope
TT-munching: recursion for tokens-as-data
TT-munching processes a token stream one token tree at a time, prepending it to an accumulator and recursing on the rest. Canonical shape :
macro_rules! count_tts {
() => { 0_usize };
( $head:tt $( $tail:tt )* ) => { 1_usize + count_tts!( $( $tail )* ) };
}
const N: usize = count_tts!(a b c d e); // 5
Rules :
- ALWAYS provide a base case (matcher with zero tokens) BEFORE the recursive case.
- ALWAYS use
:tt for pass-through tokens. Other fragment specifiers consume too much and break further recursion.
- NEVER recurse beyond what is necessary. Deep recursion hits the crate-level
recursion_limit (default 128). Raise it explicitly at the crate root: #![recursion_limit = "256"].
- PREFER the metavariable expression
${count($x)} (Rust 1.86+) over hand-rolled token counters. TT-munching for counting is now a legacy pattern.
- Use
${ignore($x)} to bind a metavariable to a repetition without expanding it (e.g. when generating fresh tokens per iteration).
Source: Rust Reference, macros-by-example Source: Little Book of Rust Macros, counting
Edition 2024: expr widening
In edition 2024 the expr fragment specifier was widened to also accept :
- the underscore expression
_ at the top level
- the const block expression
const { ... } at the top level
Pre-2024 (expr_2021) accepted neither at the top level.
// Edition 2024 only:
macro_rules! capture {
( $e:expr ) => { /* ... */ };
}
capture!(_); // OK in 2024, rejected in 2021
capture!(const { 1 + 2 }); // OK in 2024, rejected in 2021
Migration rules :
- ALWAYS use plain
expr for new edition-2024 macros.
- Use
expr_2021 ONLY when emulating the pre-2024 narrower matcher (rare; typically when a macro accepts both expr and a follow-on _ token and would now be ambiguous).
- When porting a public macro to edition 2024, audit existing arms: an
expr arm may now greedily capture inputs that previously fell through to a later arm.
Source: Rust Edition Guide, expr fragment Source: Rust 1.85 release notes
Anti-patterns (summary, see references/anti-patterns.md for the full list)
- Relying on caller-defined names: macros assume the caller has a
result variable in scope. Mixed-site hygiene defeats this. ALWAYS bind your own variables in the transcriber.
- Missing
$crate in #[macro_export]: works from the defining crate, breaks from every consumer. ALWAYS prefix internal items with $crate::.
- Wrong fragment specifier (
ident vs expr): ident rejects foo.bar, foo(), &x. Use expr when you accept any expression; reserve ident for single-token names.
- Hitting the recursion limit on simple counters: hand-rolled TT-munching counters blow the recursion budget. Use
${count($x)} (Rust 1.86+).
- Edition 2024
expr overreach: a permissive expr arm shadows what used to fall through to a later arm. Audit arm ordering after migration.
- Three-deep macro nesting where a function would do: declarative macros are for syntactic patterns and code generation. NEVER chain macros to express runtime logic: write a
fn.
Reference files
references/methods.md: full grammar tables, every fragment specifier with edition deltas, full repetition operator semantics, full follow-set table, metavariable-expression catalog.
references/examples.md: runnable examples: trivial macro, vec![...]-style builder, hashmap-literal, TT-munching counter, ${count(...)} counter, $crate cross-crate macro, hygiene demo, edition 2024 expr widening demo.
references/anti-patterns.md: eight failure modes with reproductions, root causes, and the deterministic fix for each.
1---2name: rust-syntax-macros-declarative3description: Use when the user writes a `macro_rules!`, debugs a macro hygiene issue, counts tokens via TT-munching, picks fragment specifiers, handles repetitions, or uses `$crate` for cross-crate macro references. Prevents fragment-specifier mismatches, identifier hygiene mistakes, missing `$crate` in exported macros, and naive recursion limits. Covers: `macro_rules!` declaration, all fragment specifiers (ident, expr, ty, pat, path, block, stmt, item, tt, meta, vis, literal, lifetime), repetition `$(...)+` `$(...)*` with separators, hygiene rules, `$crate`, TT-munching recursion, edition-2024 `expr` fragment widening, `macro_rules!` import scoping. Keywords: macro_rules, "declarative macro", "macro by example", fragment specifier, "ident expr ty pat path block stmt item tt meta vis literal lifetime", repetition, "$()*", "$()+", hygiene, "$crate", "TT munching", "recursion limit", "expr fragment 2024", "macro export", "macro_export", "macro_use", "what is $tt".4license: MIT5---67# rust-syntax-macros-declarative89The **mechanics** of declarative macros (`macro_rules!`) in Rust: matcher and transcriber syntax, every fragment specifier, repetition operators, follow-set restrictions, mixed-site hygiene, `$crate`, TT-munching recursion, `#[macro_export]` and `#[macro_use]` scoping, and the edition 2024 `expr` widening.1011Cross-references: [[rust-syntax-macros-procedural]] (when to graduate to proc-macros) [[rust-syntax-edition-2024]] (edition 2024 `expr` widening, `pat` 2021 widening) [[rust-syntax-pattern-matching]] (same matcher-style mindset).1213---1415## When to use this skill1617- User writes a `macro_rules! my_macro { ... }` and the matcher does not compile or does not match the call site18- User asks "which fragment specifier should I use for X" (ident vs expr vs tt vs path)19- User writes a repetition `$( ... )*` and gets E0617 ("ambiguity") or duplicate-binding errors20- User writes `#[macro_export]` and the macro fails to find its own helper items from a downstream crate21- User implements a counter / TT-muncher and hits "recursion limit reached"22- User upgrades to edition 2024 and an `expr` fragment now matches `_` or `const { ... }` where it did not before23- User asks "what is `$crate`", "what is `$tt`", "why does my macro see my outer `x` and not the caller's `x`"24- User mixes `#[macro_use]` with the 2018+ path-based import and is confused which one to use2526For procedural macros (`#[derive(...)]`, attribute macros, function-like proc-macros) see [[rust-syntax-macros-procedural]]. For edition 2024 specifics see [[rust-syntax-edition-2024]].2728---2930## Quick reference: fragment specifiers3132| Specifier | Matches | Follow-set (tokens that may follow `$x:F`) |33|---|---|---|34| `block` | a brace-delimited block expression `{ ... }` | any token |35| `expr` | an expression (edition 2024: also `_` and `const { ... }` at top level) | `=>`, `,`, `;` |36| `expr_2021` | an expression excluding top-level `_` and `const { ... }` | `=>`, `,`, `;` |37| `ident` | an identifier or keyword (not `_`, not `$crate`) | any token |38| `item` | a top-level item (fn, struct, impl, mod, ...) | any token |39| `lifetime` | a `'a` style lifetime token | any token |40| `literal` | a literal optionally preceded by `-` | any token |41| `meta` | the contents of an attribute (`#[meta]`) | any token |42| `pat` | a pattern, including top-level `\|` (since edition 2021) | `=>`, `,`, `=`, `\|`, `if`, `in` |43| `pat_param` | a pattern WITHOUT top-level `\|` | `=>`, `,`, `=`, `\|`, `if`, `in` |44| `path` | a TypePath-style path (`std::vec::Vec`, `crate::m::T`) | `=>`, `,`, `=`, `\|`, `;`, `:`, `>`, `>>`, `[`, `{`, `as`, `where` |45| `stmt` | a statement WITHOUT trailing `;` (the matcher does not consume the `;`) | `=>`, `,`, `;` |46| `tt` | a single token tree (one token, or a matched delimiter group) | any token |47| `ty` | a type | `=>`, `,`, `=`, `\|`, `;`, `:`, `>`, `>>`, `[`, `{`, `as`, `where` |48| `vis` | a possibly empty visibility (`pub`, `pub(crate)`, `pub(in ...)`, or nothing) | `,`, an identifier, a keyword that begins an item, `(`, `[`, `{` |4950ALWAYS pick the most specific fragment that works. NEVER reach for `tt` unless you genuinely need raw token capture (TT-munching, pass-through). `tt` defeats most safety the macro system gives you because it accepts anything but loses semantic shape.5152ALWAYS match the follow-set: writing `$e:expr $name:ident` is rejected because no token may follow `expr` other than `=>`, `,`, or `;`. Insert a literal separator (e.g. `,`) between fragments to make the call site unambiguous.5354[Source: Rust Reference, macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html)5556---5758## Decision tree: pick a fragment specifier5960```61Capturing a name (variable, fn, type-name token)?62├── one identifier → ident63└── a path with :: → path6465Capturing a value or computation?66├── any expression → expr (or expr_2021 if you need pre-2024 semantics)67├── a block { ... } → block68└── a statement, no `;` → stmt6970Capturing a type position?71└── any type → ty7273Capturing a pattern?74├── allow `A | B` → pat75└── single alternative → pat_param7677Capturing an attribute body?78└── meta7980Capturing literals?81└── literal8283Capturing lifetimes (`'a`)?84└── lifetime8586Capturing visibility (pub, pub(crate), empty)?87└── vis8889Capturing an item (fn, struct, impl)?90└── item9192Capturing arbitrary tokens (TT-munching, pass-through)?93└── tt (last resort)94```9596---9798## Minimal `macro_rules!` syntax99100```rust101macro_rules! my_macro {102 ( $name:ident, $value:expr ) => {103 let $name = $value;104 };105}106107fn main() {108 my_macro!(x, 1 + 2);109 println!("{x}");110}111```112113Grammar (from the Rust Reference):114115```text116MacroRulesDef := ( MacroRules ) ; // or [ MacroRules ] ; or { MacroRules }117MacroRules := MacroRule ( ; MacroRule )* ;?118MacroRule := MacroMatcher => MacroTranscriber119MacroMatcher := ( MacroMatch* ) // or [ ... ] or { ... }120MacroTranscriber:= DelimTokenTree121```122123Rules :1241251. Multiple `(matcher) => { transcriber }` arms are allowed; first matching arm wins.1262. The matcher delimiter is independent of the call-site delimiter. `my_macro!(x)`, `my_macro![x]`, and `my_macro!{x}` all parse the same matcher.1273. Metavariables are written `$name:fragment` in the matcher and `$name` in the transcriber.1284. Literal tokens in the matcher MUST appear verbatim at the call site (commas, semicolons, keywords, punctuation).129130[Source: Rust Reference, macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html)131132---133134## Repetition: `$( ... )*`, `$( ... )+`, `$( ... )?`135136```rust137macro_rules! vec_of {138 ( $( $x:expr ),* $(,)? ) => {{139 let mut v = Vec::new();140 $( v.push($x); )*141 v142 }};143}144145let v: Vec<i32> = vec_of!(1, 2, 3,);146```147148Rules :1491501. **`*` = zero or more**, **`+` = one or more**, **`?` = zero or one**.1512. A separator token (e.g. `,`) goes between `)` and the repetition operator: `$( $x:expr ),*`.1523. `?` repetition MUST NOT have a separator. `$(,)?` is correct (one optional comma); `$( $x:expr ),?` is rejected.1534. The transcriber MUST mirror the same repetition shape: `$( v.push($x); )*` mirrors the `$( $x:expr ),*` matcher.1545. Multiple metavariables inside one repetition group MUST have the same length: `$( $k:ident = $v:expr ),*` binds `$k` and `$v` together.1556. Nested repetitions are allowed: `$( $( $x:expr ),* );*`: outer `;`-separated groups of inner `,`-separated lists.156157ALWAYS allow a trailing separator at the call site using `$(,)?` (or `$(;)?`). Users expect `vec![1, 2, 3,]` to parse.158159[Source: Rust Reference, macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html)160161---162163## Metavariable expressions (stable Rust 1.86+)164165Inside the transcriber, the following expressions read repetition state:166167| Expression | Meaning |168|---|---|169| `${count($x)}` | total number of times `$x` was matched (outermost) |170| `${count($x, depth)}` | count at a given nesting depth |171| `${index()}` / `${index(depth)}` | current zero-based repetition index |172| `${len()}` / `${len(depth)}` | length of the current repetition |173| `${ignore($x)}` | mention `$x` to bind it to the surrounding repetition without expanding it |174| `$$` | literal `$` token (escape) |175176```rust177macro_rules! enumerated {178 ( $( $name:ident ),* $(,)? ) => {179 $( println!("{} = {}", ${index()}, stringify!($name)); )*180 };181}182183enumerated!(alpha, beta, gamma);184// 0 = alpha 1 = beta 2 = gamma185```186187ALWAYS use `${count(...)}` instead of hand-rolled TT-munching when you only need the count. NEVER recurse for what a metavariable expression handles directly.188189[Source: Little Book of Rust Macros, metavariable expressions](https://lukaswirth.dev/tlborm/decl-macros/macros-methodical.html)190191---192193## Hygiene: mixed-site, in plain language194195Macros by example have **mixed-site hygiene** :196197- **Local variables, loop labels, and block labels** resolve at the **macro definition site**.198- **All other symbols** (functions, types, modules, traits, statics) resolve at the **macro invocation site**.199200```rust201let x = 1;202fn func() { unreachable!("definition site") }203204macro_rules! check {205 () => {206 assert_eq!(x, 1); // `x` is the definition-site x (== 1)207 func(); // `func` is whatever func() exists at the call site208 };209}210211fn main() {212 let x = 2;213 fn func() { /* called by check!() */ }214 check!(); // assert passes; the call-site func() runs215}216```217218Rules :2192201. NEVER assume a local variable name in the caller's scope. If the macro needs a variable, declare it inside the transcriber.2212. NEVER assume a type or function is in scope at the call site. Use absolute paths via `$crate::...` (see next section).2223. ALWAYS test the macro from a different crate AND from a different module to confirm hygiene assumptions.223224[Source: Rust Reference, macros-by-example § Hygiene](https://doc.rust-lang.org/reference/macros-by-example.html)225226---227228## `$crate`: the absolute path to the defining crate229230When a `#[macro_export]` macro references its own crate's items, ALWAYS use `$crate::...`. Plain `crate::...` resolves at the **invocation site**, not the definition site, so it breaks the moment the macro is called from another crate.231232```rust233// In crate `helper`:234pub mod internals {235 pub fn helper_fn() {}236}237238#[macro_export]239macro_rules! call_helper {240 () => { $crate::internals::helper_fn() };241}242```243244```rust245// In a downstream crate:246use helper::call_helper;247248fn main() {249 call_helper!(); // expands to `helper::internals::helper_fn()` -- correct250}251```252253Rules :2542551. ALWAYS write `$crate::path::to::item` inside `#[macro_export]` macros.2562. `$crate` does NOT bypass visibility. `helper_fn` MUST still be `pub` (and reachable through `pub mod`).2573. `$crate` is itself a token, so `${ignore($crate)}` is illegal: treat it as a path prefix only.258259[Source: Rust Reference, macros-by-example § `$crate`](https://doc.rust-lang.org/reference/macros-by-example.html)260261---262263## Scoping: `#[macro_export]` vs `#[macro_use]` vs path imports264265| Mechanism | Scope | When to use |266|---|---|---|267| `#[macro_export]` on the macro | exports the macro at the **crate root** | crates that publish macros for downstream users |268| `use crate::path::my_macro;` (2018+) | bring an exported macro into scope by path | the modern way to consume macros |269| `#[macro_use] extern crate foo;` | textual import of every exported macro of `foo` | legacy 2015 edition only |270| `#[macro_use] mod m;` | textual import of every `macro_rules!` from a child module | discouraged; prefer `pub(crate) use m::name;` |271272ALWAYS prefer `pub use crate::path::name;` (edition 2018+) for both internal and external macro re-export. `#[macro_use]` is order-sensitive and brittle; reserve it for crates that still ship as 2015.273274A `macro_rules!` macro without `#[macro_export]` is private to the module that defined it. Re-export it with `pub(crate) use my_macro;` or `pub use my_macro;` to widen its scope without exposing it at the crate root.275276[Source: Rust Reference, macros § Path-Based Scope](https://doc.rust-lang.org/reference/macros-by-example.html)277278---279280## TT-munching: recursion for tokens-as-data281282TT-munching processes a token stream one token tree at a time, prepending it to an accumulator and recursing on the rest. Canonical shape :283284```rust285macro_rules! count_tts {286 () => { 0_usize };287 ( $head:tt $( $tail:tt )* ) => { 1_usize + count_tts!( $( $tail )* ) };288}289290const N: usize = count_tts!(a b c d e); // 5291```292293Rules :2942951. ALWAYS provide a base case (matcher with zero tokens) BEFORE the recursive case.2962. ALWAYS use `:tt` for pass-through tokens. Other fragment specifiers consume too much and break further recursion.2973. NEVER recurse beyond what is necessary. Deep recursion hits the crate-level `recursion_limit` (default 128). Raise it explicitly at the crate root: `#![recursion_limit = "256"]`.2984. PREFER the metavariable expression `${count($x)}` (Rust 1.86+) over hand-rolled token counters. TT-munching for counting is now a legacy pattern.2995. Use `${ignore($x)}` to bind a metavariable to a repetition without expanding it (e.g. when generating fresh tokens per iteration).300301[Source: Rust Reference, macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html) [Source: Little Book of Rust Macros, counting](https://lukaswirth.dev/tlborm/decl-macros/building-blocks/counting.html)302303---304305## Edition 2024: `expr` widening306307In edition 2024 the `expr` fragment specifier was widened to also accept :308309- the underscore expression `_` at the top level310- the const block expression `const { ... }` at the top level311312Pre-2024 (`expr_2021`) accepted neither at the top level.313314```rust315// Edition 2024 only:316macro_rules! capture {317 ( $e:expr ) => { /* ... */ };318}319320capture!(_); // OK in 2024, rejected in 2021321capture!(const { 1 + 2 }); // OK in 2024, rejected in 2021322```323324Migration rules :3253261. ALWAYS use plain `expr` for new edition-2024 macros.3272. Use `expr_2021` ONLY when emulating the pre-2024 narrower matcher (rare; typically when a macro accepts both `expr` and a follow-on `_` token and would now be ambiguous).3283. When porting a public macro to edition 2024, audit existing arms: an `expr` arm may now greedily capture inputs that previously fell through to a later arm.329330[Source: Rust Edition Guide, expr fragment](https://doc.rust-lang.org/edition-guide/rust-2024/macro-fragment-specifiers.html) [Source: Rust 1.85 release notes](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)331332---333334## Anti-patterns (summary, see references/anti-patterns.md for the full list)3353361. **Relying on caller-defined names**: macros assume the caller has a `result` variable in scope. Mixed-site hygiene defeats this. ALWAYS bind your own variables in the transcriber.3372. **Missing `$crate` in `#[macro_export]`**: works from the defining crate, breaks from every consumer. ALWAYS prefix internal items with `$crate::`.3383. **Wrong fragment specifier (`ident` vs `expr`)**: `ident` rejects `foo.bar`, `foo()`, `&x`. Use `expr` when you accept any expression; reserve `ident` for single-token names.3394. **Hitting the recursion limit on simple counters**: hand-rolled TT-munching counters blow the recursion budget. Use `${count($x)}` (Rust 1.86+).3405. **Edition 2024 `expr` overreach**: a permissive `expr` arm shadows what used to fall through to a later arm. Audit arm ordering after migration.3416. **Three-deep macro nesting where a function would do**: declarative macros are for syntactic patterns and code generation. NEVER chain macros to express runtime logic: write a `fn`.342343---344345## Reference files346347- `references/methods.md`: full grammar tables, every fragment specifier with edition deltas, full repetition operator semantics, full follow-set table, metavariable-expression catalog.348- `references/examples.md`: runnable examples: trivial macro, `vec![...]`-style builder, hashmap-literal, TT-munching counter, `${count(...)}` counter, `$crate` cross-crate macro, hygiene demo, edition 2024 `expr` widening demo.349- `references/anti-patterns.md`: eight failure modes with reproductions, root causes, and the deterministic fix for each.