C.W.K.
Stream
Lesson 02 of 04 · published

Declarative Macros (macro_rules!)

~11 min · macros, declarative, macro-rules

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

The everyday way to write a macro is macro_rules! — a declarative macro that works by pattern matching on syntax, much like match works on values.

Pattern-matching on tokens

macro_rules! defines rules: each is a pattern of tokens and the code to generate when input matches. The patterns use metavariables like $x:expr (match an expression), $n:ident (an identifier), $t:ty (a type). When you invoke the macro, the compiler matches your input against the rules and expands the matching one.

Repetition

The real power is repetition: $( $x:expr ),* matches a comma-separated list of expressions, and you expand over them with $( ... )* in the output. That's how a macro accepts any number of arguments — it's literally how vec! is built. One pattern handles zero, one, or a hundred elements.

Read $name:fragment as 'capture this kind of syntax.' $x:expr captures an expression you can paste into the output; $( ... ),* captures and repeats a comma-separated list. Once you see that macro_rules! is just match-on-syntax with repetition, the alien syntax becomes readable.

When to write one

A declarative macro is the right tool for reducing boilerplate that varies by syntax, not just by value — building a collection with custom syntax, generating repetitive trait impls, a small DSL. If you find yourself copy-pasting code that differs only in a token, a macro might be the cure — after you've ruled out a function or generic.

Code

macro_rules!: pattern + repetition build a vec! clone·rust
// A mini-vec macro: accepts any number of comma-separated expressions
macro_rules! my_vec {
    ( $( $x:expr ),* ) => {{
        let mut v = Vec::new();
        $( v.push($x); )*   // expand the push for each captured $x
        v
    }};
}

fn main() {
    let v: Vec<i32> = my_vec![10, 20, 30];
    println!("{v:?}"); // [10, 20, 30]
    let empty: Vec<i32> = my_vec![];
    println!("{empty:?}"); // []
}

External links

Exercise

Write a max! macro that takes two or more expressions and expands to the maximum, using repetition. Test it with max!(3, 7, 2). Then ask: could this have been a generic function instead? When would the macro be the better choice?
Hint
A generic fn max(a: T, b: T) handles two values cleanly; the macro earns its keep only if you want the variadic max!(a, b, c, d) syntax a function can't express. If two args suffice, the function is simpler.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.