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

Returning values

A macro gives a value back to its caller with @return:

macro max(a: int, b: int) -> int {
    @if a > b {
        @return a
    }
    @return b
}

macro show(value: int) {
    @emit value
}

show max(3, 9)
9

-> int declares the return type. It’s optional, but if it’s there, it’s enforced. It’s also needed when the macro is passed as an argument (see Macros as parameters) or used as a conversion.

The returned value must have the declared type. As with arguments, nothing is converted automatically:

from std.binary import bits

macro byte() -> bits<8> {
    @return 5
}

macro emit_byte(b: bits<8>) {
    @emit b
}

emit_byte byte()
`byte` returned `int`, but its signature declares `-> bits<8>`

Write @return 5 as bits<8> instead.

-> is only about returning. A macro that declares -> T must return a T, so declaring a return type on a macro that only emits is an error:

from std.binary import bits

macro nop() -> bits<8> {
    @emit 0x90 as bits<8>
}

nop
`nop` returned nothing, but its signature declares `-> bits<8>`

To declare what a macro emits, use the emits facet instead: macro nop() | emits bits<8> { ... }.

Returning versus emitting

Every macro has two separate outputs:

@return@emit
Goes toThe caller, as the call’s valueThe program’s output
How manyOne value, or noneAny number
Ends the macroYesNo

A macro may do both. A macro that only emits is like an instruction; a macro that only returns is like a function.

Where emitted values can go

A statement call puts the macro’s emitted values in the output where the call appears. A call used as an expression has nowhere to put emitted values unless it’s the whole of one of these:

  • a const’s value: const x = f()
  • @return’s value: @return f()

In both cases, the emitted values are kept, in order, where that statement appears. The same goes for @fold in expression position.

macro emit_and_return(x: int) -> int {
    @emit x
    @return x * 10
}

macro caller() {
    const y = emit_and_return(1)
    @emit y
}

caller
1 10

Anywhere else, such as inside a larger expression, calling a macro that emits is an error, because its values would be lost:

macro emit_and_return(x: int) -> int {
    @emit x
    @return x * 10
}

macro caller() {
    @emit 1 + emit_and_return(1)
}

caller
`emit_and_return` emits values, so it can only be used as a statement

Macros that return nothing

Using a macro that returns nothing as a value is an error. A bare @return ends a macro early without a value.