Macros

Declarative Macros

A declarative macro ("macro by example", defined with macro_rules!) transforms syntax by matching input tokens against one or more pattern rules and substituting the matched fragments into a template transcriber, operating much like a compile-time match expression over syntax trees.

Anatomy of a Declarative Macro

macro_rules! macro_name {
    ( pattern_1 ) => { expansion_1 };
    ( pattern_2 ) => { expansion_2 };
}
      ^^^^^^^^^        ^^^^^^^^^^^
       Matcher         Transcriber
  1. Matcher: Describes the expected token structure and captures syntax fragments into metavariables ($name:designator, such as $x:expr or $t:ty).
  2. Transcriber: Emits the replacement Rust code, substituting $name wherever it appears.
  3. Top-down rule evaluation: The compiler tries each arm from top to bottom and expands the first arm whose matcher matches the input tokens completely.

Invoking a Declarative Macro with (), [], or {}

Every function-like macro in Rust can be invoked using parentheses (...), square brackets [...], or curly braces {...}:

  • my_macro!(...) and my_macro![...] require a trailing semicolon ; when used as a statement.
  • my_macro! { ... } can omit the trailing semicolon when used as an item or statement block.
macro_rules! map_pair {
    ($k:expr => $v:expr) => {
        ($k, $v)
    };
}

fn main() {
    // All three delimiter styles are valid and equivalent:
    let a = map_pair!("host" => "localhost");
    let b = map_pair!["port" => 8080];
    let c = map_pair! { "tls" => true };

    println!("a = {a:?}, b = {b:?}, c = {c:?}");
}
a = ("host", "localhost"), b = ("port", 8080), c = ("tls", true)

Context-Sensitive Expansion (Expressions, Statements, Items, Patterns)

Depending on where a declarative macro is invoked in source code, its transcriber can expand into:

  • An expression: let x = vec![1, 2, 3];
  • One or more statements: println!("hi");
  • Module-level items: struct, enum, fn, or impl blocks
  • A type or pattern: inside a type annotation or match arm
// Expands into a module-level newtype struct and a constructor method:
macro_rules! define_id_type {
    ($name:ident) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
        struct $name(u64);

        impl $name {
            const fn new(raw: u64) -> Self {
                Self(raw)
            }
        }
    };
}

define_id_type!(OrderId);
define_id_type!(CustomerId);

fn main() {
    let order = OrderId::new(1001);
    let customer = CustomerId::new(1001);

    // `OrderId` and `CustomerId` are distinct types that cannot be mixed up:
    println!("order = {order:?}, customer = {customer:?}");
}
order = OrderId(1001), customer = CustomerId(1001)