Higiene, Recursión y Argumentos de Bloque
En esta página
Higiene
Las variables creadas con let dentro del cuerpo de un macro reciben un
sufijo único en la expansión. Esto significa que un let result dentro
del macro nunca sobreescribe un result que ya existe en el ámbito
del llamador. Puedes reutilizar nombres obvios como result, tmp e i
sin temor a colisiones:
twice! e inc_tmp!/dec_tmp! demuestran que cada expansión aísla sus propios bindings.
// Feature: Macro hygiene — internal vars do not leak to the caller
// Syntax: variables declared with `let` inside the body get a unique
// suffix on expansion, avoiding collisions with external names.
// When to use: you can reuse "obvious" names like `result`, `tmp`, `i`
// inside macros without fear of overwriting the caller's bindings.
// The macro declares `result` internally.
macro twice(x) {
let result = $x * 2
print("inside the macro: {result}")
}
// The caller also has `result` — it is not overwritten.
let result = 100
twice!(7)
print("outside: {result}")
// expected:
// inside the macro: 14
// outside: 100
// The same `tmp` name in three different macros — no collision.
macro inc_tmp(x) {
let tmp = $x + 1
print("inc: {tmp}")
}
macro dec_tmp(x) {
let tmp = $x - 1
print("dec: {tmp}")
}
let tmp = 999
inc_tmp!(10)
dec_tmp!(10)
print("original tmp: {tmp}")
// expected:
// inc: 11
// dec: 9
// original tmp: 999
Recursión
Un macro puede invocar a otro macro en su cuerpo. La expansión se
resuelve recursivamente (límite de ~64 niveles). Esto permite componer
reglas pequeñas en lugar de duplicar lógica — min3! reutiliza min2!,
y min4! reutiliza ambos:
Cadena min2! → min3! → min4! y composición con double!(inc!(5)).
// Feature: Recursive macros — one macro calling another
// Syntax: the `outer` macro invokes `inner!(...)` in its body
// When to use: compose small macros to avoid duplicated code, modular
// textual rules (depth limit ~64).
// Basic block: minimum of two.
macro min2(a, b) {
if $a < $b { $a } else { $b }
}
// Reuses min2 to build min3.
macro min3(a, b, c) {
min2!($a, min2!($b, $c))
}
// And min4 reuses min3 + min2.
macro min4(a, b, c, d) {
min2!(min2!($a, $b), min2!($c, $d))
}
print(min2!(7, 3))
// expected: 3
print(min3!(7, 2, 5))
// expected: 2
print(min4!(9, 4, 6, 1))
// expected: 1
// Composition also works with different expressions at each level.
macro double(x) {
$x + $x
}
macro inc(x) {
$x + 1
}
print(double!(inc!(5)))
// expected: 12 ((5 + 1) + (5 + 1))
Bloques como argumento
Cualquier parámetro puede recibir un bloque { ... }. En la expansión,
el bloque se pega textualmente donde aparece $param, creando
construcciones de control personalizadas. repeat_n! implementa un
bucle; logged! envuelve el cuerpo con mensajes de inicio y fin:
repeat_n!(n, { ... }) y logged!(label, { ... }) como constructores de flujo de control.
// Feature: Macros that receive a block as an argument
// Syntax: `name!(arg, { ...statements... })` — any arg can be a block
// inside `{}`, expanded textually wherever `$arg` appears.
// When to use: build custom "control-flow constructs" — retry, timed,
// with_lock, etc. — where the user passes the "body".
// Repeats a block N times, counting attempts.
macro repeat_n(times, body) {
var __i = 0
while __i < $times {
__i = __i + 1
$body
}
}
repeat_n!(3, {
print("tick")
})
// expected:
// tick
// tick
// tick
// Macro that wraps the body with try-style logging.
macro logged(label, body) {
print("[start] {$label}")
$body
print("[done] {$label}")
}
logged!("important work", {
let x = 10 + 20
print("computed {x}")
})
// expected:
// [start] important work
// computed 30
// [done] important work
Desafío
Crea un macro timed!(label, body) que imprima "[start] label",
ejecute body, y luego imprima "[end] label". Úsalo para envolver dos
bloques distintos y verifica que los mensajes aparecen correctamente
intercalados.
Consulta también