Skip to content

macro_rules!

macro_rules! is Zolo's second macro form, inspired by Rust. The fundamental difference from macro name(p) { ... } is that you write pattern arms — the expander tests each arm top to bottom and uses the first one that matches. This enables arity overloading, variadic repetition, and capture constraints.

Basic form

The simplest arm captures nothing: () => { expression }. Invocation uses braces: name!{}:

greet!{} with no parameters — the minimal form of macro_rules!.

09-macro-rules-basic.zolo
Playground
// Feature: `macro_rules!` — pattern-matched macros (Rust-style)
// Syntax:
//   macro_rules! name {
//       () => { <expansion> }
//   }
// Invoked as `name!{...}`. Each `(...) => {...}` arm is a pattern;
// the engine tries them in order and picks the first match.
// When to use: short syntactic helpers where capture-by-position
// matters more than function-call semantics. Compare with the
// older `macro name(x) { ... }` form (files 01-08 in this folder)
// that captures by name.

macro_rules! greet {
    () => { "hello, world" }
}

print(greet!{})
// expected: hello, world

Repetition

The pattern $( $x:expr ),* captures zero or more comma-separated expressions. In the body, $( body )* repeats once per captured item. This is the mechanism behind variadic macros like sum!{a, b, c}:

sum! accumulates any number of expressions with $( ... ),*.

10-macro-rules-repetition.zolo
Playground
// Feature: repetition — `$( ... ),*` matches zero or more, comma-separated

// Syntax: inside the pattern, `$( $x:expr ),*` captures every comma-

// separated `expr`; inside the body, `$( body )*` repeats once per match.

// When to use: variadic macros — `sum!{a, b, c}`, `vec![1, 2, 3]`-style

// constructors, `concat!`-like builders.


macro_rules! sum {
    ($($x:expr),*) => {{
        var acc = 0
        $( acc = acc + $x; )*
        acc
    }}
}

print(sum!{1, 2, 3, 4})
// expected: 10


print(sum!{10, 20})
// expected: 30

Multiple arms and overloading

By listing several arms, you cover different arities in the same macro. The expander picks the first arm whose pattern fits — identical behavior to match:

describe! with zero, one, and two arguments — the first matching arm wins.

11-macro-rules-overload.zolo
Playground
// Feature: multiple arms — pick the first pattern that matches
// Syntax: list several `(...) => {...}` arms. The expander tries
// them top-to-bottom and uses the first one that fits.
// When to use: arity-overloaded macros — `assert!(cond)` vs.
// `assert!(cond, msg)`, default-argument convenience wrappers.

macro_rules! describe {
    () => { "nada" }
    ($x:expr) => { "um item" }
    ($x:expr, $y:expr) => { "dois itens" }
}

print(describe!{})
// expected: nada

print(describe!{42})
// expected: um item

print(describe!{1, 2})
// expected: dois itens

Hygiene in macro_rules!

The same hygiene guarantee from the macro form applies here: internal let bindings receive a unique suffix and never overwrite the caller's bindings:

double_sum! declares acc internally without affecting the caller's acc.

12-macro-rules-hygiene.zolo
Playground
// Feature: hygiene under `macro_rules!` — internal vars never leak

// Syntax: any `let` bound inside the expansion gets a unique suffix,

// so the caller's `acc` is not overwritten when the macro also

// declares `acc`.

// When to use: any macro that needs scratch state. Hygiene is on by

// default; you don't have to do anything to opt in.


macro_rules! double_sum {
    ($x:expr) => {{
        let acc = $x
        acc + acc
    }}
}

let acc = 100
let result = double_sum!{5}

// The macro's internal `acc` did not overwrite the caller's `acc`.

print(acc)       // expected: 100

print(result)    // expected: 10

Capture kinds

The suffix after : restricts the token type accepted in the pattern: :expr accepts any expression, :ident only an identifier, :lit only a literal (number, string, bool, nil). This allows more precise misuse errors at expansion time:

show_lit! with :lit and choose! with :expr illustrate capture constraints.

13-macro-rules-capture-kinds.zolo
Playground
// Feature: capture kinds — `:expr`, `:ident`, `:lit`

// Syntax: in a pattern, `$name:kind` constrains what kind of token

// can match. `:expr` accepts any expression, `:ident` only an

// identifier, `:lit` only a literal (number, string, bool, nil).

// When to use: write tighter macros that catch misuse early. A

// `getter!(field)` macro should take an `:ident`, not an `:expr`.


macro_rules! show_lit {
    ($x:lit) => { $x }
}

macro_rules! choose {
    ($x:expr, $y:expr) => { if true { $x } else { $y } }
}

// `:lit` accepts numbers, strings, bools, nil.

print(show_lit!{42})
// expected: 42


print(show_lit!{"hi"})
// expected: hi


// `:expr` accepts arbitrary expressions.

print(choose!{10, 20})
// expected: 10


// You can build conditions inline.

print(choose!{1 + 2, 99})
// expected: 3

Challenge

Rewrite the assert! macro from the previous chapter using macro_rules! with two arms: ($cond:expr) with a default message and ($cond:expr, $msg:lit) with a custom message. Verify that both forms work.

enespt-br