Skip to content

Guide 17 of 36

Macros

Hygienic macros, parameters, stringify! and compile-time expansion.

On this page

Zolo expands macros before type checking and lowering. Use a macro when syntax must be generated or inspected; use a function when ordinary values are enough.

Two declarative forms are available:

  • macro name(args) { ... } is the compact form for expression substitution.
  • macro_rules! name { ... } adds pattern arms, capture kinds, and repetition.

Both forms rename bindings introduced by an expansion so they do not overwrite same-named bindings at the call site.

Compact macro

Parameters are referenced with $name inside the macro body. Invoke the macro with name!(arguments):

macro double(value) {
    $value + $value
}

let result = double!(21)
print(result)

Output:

42

Substitution is syntactic. If an argument appears twice in the body, its expression is evaluated twice after expansion. Bind a value before invoking the macro when duplicate evaluation would be observable.

macro_rules!

macro_rules! tests its arms from top to bottom and expands the first complete match. Captures use $name:kind; supported kinds include expr, ident, ty, lit, pat, block, and tt.

Repetition uses $( ... )* or $( ... )+, with an optional separator before the repetition operator. This example accepts any number of comma-separated expressions:

macro_rules! sum {
    ($($value:expr),*) => {{
        var total = 0
        $( total = total + $value; )*
        total
    }}
}

print(sum!{1, 2, 3, 4})
print(sum!{10, 20})

Output:

10
30

Calls may use braces, brackets, or parentheses. Braces are the clearest choice for macro_rules! because parentheses are also used by the compact macro form.

Expanding declarations

A macro_rules! call that appears by itself at the top level may expand to one or more complete declarations. This is useful for small declaration families and for installing a derive producer without repeating boilerplate:

macro_rules! declare_pair {
    () => {
        struct Left { value: int }
        struct Right { value: int }
    }
}

declare_pair!{}

The expansion must parse as complete top-level items. In expression position, the same macro still has to produce an expression.

Writing a literal $

Inside a macro_rules! template, $$ emits one literal $. The escape matters when a declaration-generating macro itself emits a structured quote hole:

macro_rules! install_tag_derive {
    () => {
        @derive_for(Tag)
        fn derive_tag(info) -> Syntax<.Items> {
            return quote items { impl $${info.ref} {} }
        }
    }
}

Here macro expansion turns $${info.ref} into ${info.ref}; that hole is then evaluated later by the derive's structured-quote stage.

Hygiene

Bindings created by the expansion receive a unique internal name. Captured identifiers keep the identity of the caller's syntax. You can therefore use a scratch name such as total inside a macro without overwriting a caller's total.

Hygiene is lexical, not a type-system feature. Expansion still happens before the type checker, so invalid generated code is reported only after the macro has expanded.

Dispatch and recursion

Several arms can cover different shapes or arities. If no arm matches the entire token stream, expansion fails with a no matching rule error.

Macros can invoke other macros and can recurse. Expansion depth is capped at 64 so an accidental infinite recursion fails during compilation.

Current limits

  • Procedural @proc_macro declarations are scaffold syntax only. Their bodies are not executed to transform code, so do not use them as a public feature.
  • The declarative matcher is greedy and does not implement Rust's full backtracking behavior for deeply nested repetitions.
  • Diagnostics point at the expanded syntax in some cross-file cases; complete source-context preservation is still pending.

macro_rules! declarations are file-private unless marked pub. Public macros can be imported directly, through a list or glob, under an alias, or re-exported with pub use.

Learn more

DOCS / FEEDBACK

Did this page leave a question?

Tell us where the explanation lost you. Documentation is part of the language experience.

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

en