Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Macros

Rock has declarative token macros that generate top-level syntax before ordinary declaration collection and type checking. Macro expansion is useful when the repeated source shape cannot be expressed as a function, trait, or generic. It also makes ownership and generated control flow less obvious, so a small conventional expansion is easier to review than a clever one.

Current status: Declarative macro parsing, direct captures, expression captures, and flat repetition are covered by compiler tests. Macro use remains experimental: nested repetition, indentation-sensitive arguments, diagnostics, and formatter support are incomplete. The examples below use parser- and expansion-tested syntax; inspect generated source with rock expand before depending on it in a package.

A zero-argument declaration macro

The simplest matcher has no captures. The invocation is a top-level %name line, and the body emits an ordinary Rock declaration.

macro make_main
    =>
        main = !->
            42.println!

%make_main

The expansion produces a normal unit-returning main function, which prints 42 and exits with status 0. The macro itself is not a runtime function and has no runtime ownership effect; only the generated declarations participate in type checking and cleanup. Keep generated names unique because two expansions that emit the same top-level name create the same conflict as handwritten declarations.

Capturing identifiers and expressions

The tested fragment categories are ident, expr, and ty. The example below uses identifier and expression captures; the next example demonstrates type-name captures.

The invocation syntax is token-based and uses spaces rather than a function-call comma list. In the example below, $name:ident captures answer and $value:expr captures the expression 6 * 7:

macro make_constant
    $name:ident $value:expr =>
        $name = -> $value

%make_constant answer 6 * 7

main = !->
    answer! .println!

The generated answer returns 42, so this executable example prints 42. A macro invocation always starts with %, while an ordinary declaration would not be expanded.

Capturing a type

Use ty to capture a single uppercase type name. Rock distinguishes uppercase type-name tokens from ordinary identifiers, so both the new struct name Number and the field type I64 require ty captures. An ident capture would not match Number.

macro make_wrapper
    $name:ty $inner:ty =>
        struct $name
            < value: $inner

%make_wrapper Number I64

main = !->
    number = Number
        value: 42

Here $name:ty captures Number and $inner:ty captures I64. The expansion declares a struct with an exported value: I64 field, and main constructs a value of that struct. Captures match syntax before type checking, so Number does not need to be declared before the invocation. Currently, ty captures consume only one type-name token, not a compound type such as a reference or a generic type application.

Repetition

A repetition matcher uses $( and )*. The following syntax is covered by the expansion tests and is deliberately flat.

macro define_values
    $( $name:ident )* =>
        $( $name = -> 9 )

%define_values first second third

main = !->
    first! .println!
    second! .println!
    third! .println!

The matcher captures three identifiers and the template emits three functions. The output is 9, 9, and 9. Repetition consumes the captured tokens into generated declarations; it does not clone a runtime value and does not create a runtime loop. Captures in one repeated group must have the same number of elements.

The compiler also tests a direct capture used inside a repeated template. This form is useful when several generated functions should return one already-declared value:

macro define_aliases
    $source:ident $( $name:ident )* =>
        $( $name = -> $source! )

source = -> 7
%define_aliases source left right

main = !->
    left! .println!
    right! .println!

The output is 7 and 7. The source declaration appears before the invocation, so the generated functions have a real target and no placeholder value. Do not nest another repetition inside either matcher or template; nested repetition is a known unsupported area even though the token grammar accepts repetition groups.

Invocation matching and errors

Macro arms are tried in declaration order. A matcher that does not consume the invocation’s tokens produces a compile-time diagnostic; it does not become a runtime branch. This example has two complete arms and therefore demonstrates a deliberate fallback without relying on an undefined error type.

macro make_value
    $name:ident $value:expr =>
        $name = -> $value
    $name:ident =>
        $name = -> 0

%make_value explicit 8
%make_value fallback

main = !->
    explicit! .println!
    fallback! .println!

The output is 8 and 0. Put the more specific arm first; a broad arm can consume input before a later arm gets a chance. Invalid input is reported near the invocation, but expansion-origin labels and indentation diagnostics are still being improved.

Expansion workflow and design rules

Use rock expand to inspect generated source before debugging type or ownership errors. A generated ~@ method still consumes its receiver, and a generated mutable method still requires a mutable binding; macros do not weaken the borrow checker. Prefer ordinary functions for behavior, generics for type reuse, and a macro only when the repeated declaration shape itself is the useful abstraction.

Rock does not yet expose a stable procedural-macro, derive, or attribute API for application authors. This chapter therefore documents only declarative %name macros.