macro_rules!
Nesta página
macro_rules! é a segunda forma de macro do Zolo, inspirada no Rust. A
diferença fundamental em relação a macro name(p) { ... } é que você escreve
braços de padrão — o expansor testa cada braço de cima para baixo e usa o
primeiro que casa. Isso habilita sobrecarga de aridade, repetição variádica e
restrições de captura.
Forma básica
O braço mais simples não captura nada: () => { expressão }. Invocação usa
chaves: name!{}:
greet!{} sem parâmetros — forma mínima de 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
Repetição
O padrão $( $x:expr ),* captura zero ou mais expressões separadas por
vírgula. No corpo, $( corpo )* repete uma vez por item capturado. Esse é o
mecanismo por trás de macros variádicos como sum!{a, b, c}:
sum! acumula qualquer quantidade de expressões com $( ... ),*.
// 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
Múltiplos braços e sobrecarga
Listando vários braços, você cobre diferentes aridades no mesmo macro. O
expansor escolhe o primeiro braço cujo padrão se encaixa — comportamento
idêntico ao match:
describe! com zero, um e dois argumentos — o primeiro braço que casa vence.
// 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
Higiene em macro_rules!
A mesma garantia de higiene da forma macro se aplica aqui: let interno
recebe sufixo único e nunca sobrescreve bindings do chamador:
double_sum! declara acc internamente sem afetar o acc do chamador.
// 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
Tipos de captura
O sufixo após : restringe o tipo de token aceito no padrão: :expr aceita
qualquer expressão, :ident apenas um identificador, :lit apenas um literal
(número, string, bool, nil). Isso permite erros de misuse mais precisos em
tempo de expansão:
show_lit! com :lit e choose! com :expr ilustram as restrições de captura.
// 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
Desafio
Reescreva o macro assert! do capítulo anterior usando macro_rules! com
dois braços: ($cond:expr) com mensagem padrão e ($cond:expr, $msg:lit)
com mensagem personalizada. Verifique que ambas as formas funcionam.