macro_rules!
En esta página
macro_rules! es la segunda forma de macro de Zolo, inspirada en Rust.
La diferencia fundamental respecto a macro name(p) { ... } es que se
escriben brazos de patrón — el expansor prueba cada brazo de arriba
abajo y usa el primero que coincide. Esto habilita la sobrecarga de
aridad, la repetición variádica y las restricciones de captura.
Forma básica
El brazo más simple no captura nada: () => { expresión }. La
invocación usa llaves: name!{}:
greet!{} sin parámetros — la 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
Repetición
El patrón $( $x:expr ),* captura cero o más expresiones separadas por
coma. En el cuerpo, $( cuerpo )* se repite una vez por cada elemento
capturado. Este es el mecanismo detrás de macros variádicos como
sum!{a, b, c}:
sum! acumula cualquier cantidad de expresiones con $( ... ),*.
// 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últiples brazos y sobrecarga
Al listar varios brazos, se cubren diferentes aridades en el mismo macro.
El expansor elige el primer brazo cuyo patrón encaje — comportamiento
idéntico al de match:
describe! con cero, uno y dos argumentos — el primer brazo que coincide gana.
// 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 en macro_rules!
La misma garantía de higiene de la forma macro se aplica aquí: los
bindings internos con let reciben un sufijo único y nunca sobreescriben
los bindings del llamador:
double_sum! declara acc internamente sin afectar al acc del llamador.
// 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
El sufijo después de : restringe el tipo de token aceptado en el
patrón: :expr acepta cualquier expresión, :ident solo un
identificador, :lit solo un literal (número, cadena, bool, nil). Esto
permite errores de uso incorrecto más precisos en tiempo de expansión:
show_lit! con :lit y choose! con :expr ilustran las restricciones 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
Desafío
Reescribe el macro assert! del capítulo anterior usando macro_rules!
con dos brazos: ($cond:expr) con mensaje por defecto y
($cond:expr, $msg:lit) con mensaje personalizado. Verifica que ambas
formas funcionan.
Consulta también