Higiene, Recursão e Argumentos de Bloco
Nesta página
Higiene
Variáveis criadas com let dentro do corpo de um macro recebem um sufixo
único na expansão. Isso significa que um let result dentro do macro nunca
sobreescreve um result que já existe no escopo do chamador. Você pode
reusar nomes óbvios como result, tmp e i sem medo de colisão:
twice! e inc_tmp!/dec_tmp! demonstram que cada expansão isola seus próprios 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
Recursão
Um macro pode invocar outro macro no seu corpo. A expansão é resolvida
recursivamente (limite de ~64 níveis). Isso permite compor regras pequenas em
vez de duplicar lógica — min3! reusa min2!, e min4! reusa ambos:
Cadeia min2! → min3! → min4! e composição com 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))
Blocos como argumento
Qualquer parâmetro pode receber um bloco { ... }. Na expansão, o bloco é
colado textualmente onde $param aparece, criando construções de controle
personalizadas. repeat_n! implementa um laço; logged! envolve o corpo com
mensagens de início e fim:
repeat_n!(n, { ... }) e logged!(label, { ... }) como construtores de controle de fluxo.
// 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
Desafio
Crie um macro timed!(label, body) que imprime "[start] label", executa
body, e depois imprime "[end] label". Use-o para envolver dois blocos
diferentes e verifique que as mensagens aparecem corretamente intercaladas.
Veja também