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!.
// 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 $( ... ),*.
// 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.
// 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.
// 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.
// 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.