# Rust Syntax Macros Declarative

> 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".

- Skill: `impertio-studio/rust-syntax-macros-declarative` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/rust-syntax-macros-declarative`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/rust-syntax-macros-declarative/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/rust-syntax-macros-declarative

---


# 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](https://doc.rust-lang.org/reference/macros-by-example.html)

---

## 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

```rust
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):

```text
MacroRulesDef   := ( MacroRules ) ;     // or  [ MacroRules ] ;   or  { MacroRules }
MacroRules      := MacroRule ( ; MacroRule )* ;?
MacroRule       := MacroMatcher => MacroTranscriber
MacroMatcher    := ( MacroMatch* )      // or [ ... ]  or { ... }
MacroTranscriber:= DelimTokenTree
```

Rules :

1. Multiple `(matcher) => { transcriber }` arms are allowed; first matching arm wins.
2. 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.
3. Metavariables are written `$name:fragment` in the matcher and `$name` in the transcriber.
4. Literal tokens in the matcher MUST appear verbatim at the call site (commas, semicolons, keywords, punctuation).

[Source: Rust Reference, macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html)

---

## Repetition: `$( ... )*`, `$( ... )+`, `$( ... )?`

```rust
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 :

1. **`*` = zero or more**, **`+` = one or more**, **`?` = zero or one**.
2. A separator token (e.g. `,`) goes between `)` and the repetition operator: `$( $x:expr ),*`.
3. `?` repetition MUST NOT have a separator. `$(,)?` is correct (one optional comma); `$( $x:expr ),?` is rejected.
4. The transcriber MUST mirror the same repetition shape: `$( v.push($x); )*` mirrors the `$( $x:expr ),*` matcher.
5. Multiple metavariables inside one repetition group MUST have the same length: `$( $k:ident = $v:expr ),*` binds `$k` and `$v` together.
6. 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](https://doc.rust-lang.org/reference/macros-by-example.html)

---

## 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) |

```rust
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](https://lukaswirth.dev/tlborm/decl-macros/macros-methodical.html)

---

## 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**.

```rust
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 :

1. NEVER assume a local variable name in the caller's scope. If the macro needs a variable, declare it inside the transcriber.
2. NEVER assume a type or function is in scope at the call site. Use absolute paths via `$crate::...` (see next section).
3. ALWAYS test the macro from a different crate AND from a different module to confirm hygiene assumptions.

[Source: Rust Reference, macros-by-example § Hygiene](https://doc.rust-lang.org/reference/macros-by-example.html)

---

## `$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.

```rust
// In crate `helper`:
pub mod internals {
    pub fn helper_fn() {}
}

#[macro_export]
macro_rules! call_helper {
    () => { $crate::internals::helper_fn() };
}
```

```rust
// In a downstream crate:
use helper::call_helper;

fn main() {
    call_helper!(); // expands to `helper::internals::helper_fn()` -- correct
}
```

Rules :

1. ALWAYS write `$crate::path::to::item` inside `#[macro_export]` macros.
2. `$crate` does NOT bypass visibility. `helper_fn` MUST still be `pub` (and reachable through `pub mod`).
3. `$crate` is itself a token, so `${ignore($crate)}` is illegal: treat it as a path prefix only.

[Source: Rust Reference, macros-by-example § `$crate`](https://doc.rust-lang.org/reference/macros-by-example.html)

---

## 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](https://doc.rust-lang.org/reference/macros-by-example.html)

---

## 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 :

```rust
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 :

1. ALWAYS provide a base case (matcher with zero tokens) BEFORE the recursive case.
2. ALWAYS use `:tt` for pass-through tokens. Other fragment specifiers consume too much and break further recursion.
3. 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"]`.
4. PREFER the metavariable expression `${count($x)}` (Rust 1.86+) over hand-rolled token counters. TT-munching for counting is now a legacy pattern.
5. 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](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)

---

## 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.

```rust
// 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 :

1. ALWAYS use plain `expr` for new edition-2024 macros.
2. 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).
3. 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](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/)

---

## Anti-patterns (summary, see references/anti-patterns.md for the full list)

1. **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.
2. **Missing `$crate` in `#[macro_export]`**: works from the defining crate, breaks from every consumer. ALWAYS prefix internal items with `$crate::`.
3. **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.
4. **Hitting the recursion limit on simple counters**: hand-rolled TT-munching counters blow the recursion budget. Use `${count($x)}` (Rust 1.86+).
5. **Edition 2024 `expr` overreach**: a permissive `expr` arm shadows what used to fall through to a later arm. Audit arm ordering after migration.
6. **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.

