Macros
Macros in Zolo allow you to define code-generating constructs that are expanded at compile time, before type checking and lowering. Macros are hygienic — variables introduced inside a macro body do not leak into the caller's scope.
Defining Macros #
macro log!(msg) {
print("[LOG] {$msg}")
}
log!("server started")
// expands to: print("[LOG] {server started}")
Macro names conventionally end with ! to distinguish them from regular function calls.
Parameters #
Macros accept one or more parameters prefixed with $:
macro assert_equal!(a, b) {
if $a != $b {
panic("assertion failed: {$a} != {$b}")
}
}
assert_equal!(1 + 1, 2)
assert_equal!("hello".len(), 5)
Multiple Parameters #
macro swap!(a, b) {
let $tmp = $a
$a = $b
$b = $tmp
}
let mut x = 10
let mut y = 20
swap!(x, y)
print("{x}, {y}") // 20, 10
Hygiene #
Variables introduced inside a macro body get a unique suffix to avoid colliding with caller-scope names:
macro double_print!(val) {
let result = $val * 2 // 'result' is renamed to 'result_m1' internally
print("{result}")
}
let result = 100 // this 'result' is NOT overwritten by the macro
double_print!(7) // prints 14
print("{result}") // still prints 100
Built-in: stringify!
The stringify! macro converts any expression to its source-code string representation at compile time:
let name = stringify!(hello_world)
print("{name}") // "hello_world"
let expr = stringify!(1 + 2 * 3)
print("{expr}") // "1 + 2 * 3"
This is useful for debugging and assertion messages:
macro expect!(expr) {
if !($expr) {
panic("expected: {stringify!($expr)}")
}
}
let x = 5
expect!(x > 3) // OK
expect!(x > 10) // panic: expected: x > 10
Recursive Macros #
Macros can call other macros (up to a depth limit of 64):
macro min2!(a, b) {
if $a < $b { $a } else { $b }
}
macro min3!(a, b, c) {
min2!($a, min2!($b, $c))
}
print(min3!(7, 2, 5)) // 2
Practical Examples #
Logging with context #
macro log_debug!(msg) {
print("[DEBUG] {$msg}")
}
macro log_error!(msg) {
print("[ERROR] {$msg}")
}
log_debug!("processing request")
log_error!("connection refused")
Timed block #
macro timed!(label, body) {
let t0 = now()
$body
let elapsed = now() - t0
print("{$label} took {elapsed}ms")
}
timed!("sort", {
let data = [5, 3, 8, 1]
let sorted = Array.sort(data)
})
Retry logic #
macro retry!(times, body) {
let mut attempts = 0
let mut success = false
while attempts < $times and !success {
attempts = attempts + 1
$body
success = true
}
}
retry!(3, {
fetch("https://api.example.com/data")
})
Limitations #
- Macros are textual — they operate on syntax trees, not types.
- Macros must be defined before use in the same file.
- Macros cannot be exported across modules (they are file-scoped).
- Variadic macros (variable number of arguments) are not yet supported.