Skip to content
Declarative Macros (macro_rules!)
step 1/4

Reading — step 1 of 4

Learn

~1 min readMacros and Unsafe

Rust has TWO kinds of macros:

  • Declarative (macro_rules!) — pattern matching on token trees, defined in your code
  • Procedural (proc macros) — Rust functions that take and produce token streams, in a separate crate

This lesson is about declarative macros — the simpler kind.

macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

fn main() {
    say_hello!();        // expands to println!("Hello!");
}

Pattern matching:

macro_rules! greet {
    () => {
        println!("Hello!");
    };
    ($name:expr) => {
        println!("Hello, {}!", $name);
    };
    ($greeting:expr, $name:expr) => {
        println!("{}, {}!", $greeting, $name);
    };
}

greet!();                          // "Hello!"
greet!("Ada");                     // "Hello, Ada!"
greet!("Hi", "World");              // "Hi, World!"

Fragment specifiers describe what kind of token tree to match:

  • expr — an expression
  • ident — identifier
  • ty — type
  • pat — pattern
  • stmt — statement
  • block{ ... } block
  • path::path::like::this
  • tt — single token tree (catch-all)
  • literal — literal value

Variadic with * and +:

macro_rules! vec_of_doubled {
    ($($x:expr),*) => {
        vec![$($x * 2),*]
    };
}

let v = vec_of_doubled!(1, 2, 3);   // vec![2, 4, 6]

The standard vec! macro is implemented this way — that's why vec![1, 2, 3] works.

When to use macros:

  • Reduce repetition that traits/generics can't (e.g., variable-arity)
  • DSLs (small embedded languages)
  • Compile-time guarantees (e.g., format_args! checks format strings)

Most Rust code has zero declarative macros. Reach for them when generics + traits aren't enough.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…