RustArena

Step 1 of 6

Your first macro_rules! macro

You've used macros since your first program: println!, format!, vec!, assert_eq!, todo!. A macro is code that writes code. The compiler expands it before type checking, replacing the call with the code the macro produces.

Why have macros at all?

Functions are great, but they have limits that macros don't:

The price: macros are harder to read, their errors point into generated code, and editors understand them less well. The rule teams follow is simple: if a function (or a generic, or a trait) can do it, write a function. Reach for a macro when it can't.

macro_rules!

Declarative macros are written with macro_rules!. Each rule has a matcher (the pattern the call must look like) and a transcriber (the code it expands to):

macro_rules! square {
    ($x:expr) => {
        $x * $x
    };
}

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

fn main() {
    println!("{}", square!(7));
    println!("{}", square!(2 + 3));
    greet!();
    greet!("Ferris");
}
49
25
Hello, arena!
Hello, Ferris!

Piece by piece:

Common mistakes

No rule matches the call. The error names the token where matching failed:

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

fn main() {
    greet!("Ferris", "Corro");
}
error: no rules expected `,`

Evaluating an argument twice. $x * $x pastes the expression in twice, so it runs twice. That matters when it has side effects or is expensive:

macro_rules! square {
    ($x:expr) => {
        $x * $x
    };
}

macro_rules! square_once {
    ($x:expr) => {{
        let v = $x;
        v * v
    }};
}

fn main() {
    let a = square!({ println!("computing"); 3 });
    println!("{a}");
    let b = square_once!({ println!("computing"); 3 });
    println!("{b}");
}
computing
computing
9
computing
9

The fix stores the value in a local first. Notice the double braces {{ ... }}: the outer pair belongs to the rule, the inner pair is a block expression in the expanded code, so let v and v * v become one expression.

Your task

Fill in the blanks of a cube! macro that evaluates its argument once. The program should print 27 and then 8.

Challenge 2

Write max2!(a, b), which evaluates to the larger of two values. Each argument must be evaluated exactly once (a hidden test counts the calls), so store both in locals inside a block before comparing.

Challenge 3

Matchers can contain plain tokens as well as metavariables. Write a log! macro that returns a String:

msg can be any expression that implements Display. Write the warn and error rules before the general one.

Next: Fragment specifiers and generating code