Macros

Macro Patterns

A macro pattern (the matcher on the left side of => in a macro_rules! arm) combines literal tokens, balanced delimiter groups ((), [], {}), and typed metavariables ($name:fragment) to parse custom domain-specific syntax at compile time.

Components of a Macro Matcher

  1. Metavariables ($name:specifier): Capture a specific grammatical construct (such as $id:ident, $val:expr, $ty:ty) and bind it to $name.
  2. Literal tokens and keywords: Any non-$ token in the matcher (such as =>, :, where, ->, or custom words like from and select) must appear verbatim in the macro invocation.
  3. Delimiter groups ((...), [...], {...}): Match balanced parentheses, brackets, or braces in the input token stream.
Matcher:
    ( fn $name:ident ( $arg:ident : $arg_ty:ty ) -> $ret:ty => $body:expr )
      ^^ ^^^^^^^^^^^ ^ ^^^^^^^^^^ ^ ^^^^^^^^^^ ^ ^^ ^^^^^^^ ^^ ^^^^^^^^^^
     lit   metavar  del  metavar lit  metavar del lit metavar lit metavar
macro_rules! mini_query {
    (SELECT $field:ident FROM $source:expr WHERE $item:ident => $cond:expr) => {{
        $source
            .iter()
            .filter(|$item| $cond)
            .map(|$item| $item.$field)
            .collect::<Vec<_>>()
    }};
}

struct User {
    name: &'static str,
    age: u32,
}

fn main() {
    let users = [
        User { name: "Alice", age: 30 },
        User { name: "Bob", age: 17 },
        User { name: "Carol", age: 25 },
    ];

    let adults = mini_query!(SELECT name FROM users WHERE u => u.age >= 18);
    println!("Adult names: {adults:?}");
}
Adult names: ["Alice", "Carol"]

Follow-Set Restrictions to Prevent Parse Ambiguity

Because the Rust parser parses metavariables like $e:expr or $t:ty greedily in a single pass without backtracking, macro_rules! enforces follow-set rules on what tokens may immediately follow a metavariable:

Fragment Specifier Allowed Following Tokens in Matcher
expr and stmt Only =>, , (comma), or ; (semicolon)
ty and path =>, ,, ;, =, |, :, >, >>, [, {, as, where, or a block
pat =>, ,, =, |, if, in
ident, tt, literal, block Any token (never ambiguous)

Attempting to place an arbitrary identifier or operator like + immediately after $a:expr fails at compile time because $a:expr would greedily consume + $b as part of the first expression:

// ❌ does not compile
macro_rules! bad_add {
    ($a:expr + $b:expr) => {
        $a + $b
    };
}

fn main() {}
error: `$a:expr` is followed by `+`, which is not allowed for `expr` fragments
 --> src/main.rs:3:14
  |
3 |     ($a:expr + $b:expr) => {
  |              ^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`