Macros

By now you've learned about many of the tools Rust provides for abstracting and reusing code. These units of code reuse have a rich semantic structure. For example, functions have a type signature, type parameters have trait bounds, and overloaded functions must belong to a particular trait.

This structure means that Rust's core abstractions have powerful compile-time correctness checking. But this comes at the price of reduced flexibility. If you visually identify a pattern of repeated code, you may find it's difficult or cumbersome to express that pattern as a generic function, a trait, or anything else within Rust's semantics.

Macros allow us to abstract at a syntactic level. A macro invocation is shorthand for an "expanded" syntactic form. This expansion happens early in compilation, before any static checking. As a result, macros can capture many patterns of code reuse that Rust's core abstractions cannot.

The drawback is that macro-based code can be harder to understand, because fewer of the built-in rules apply. Like an ordinary function, a well-behaved macro can be used without understanding its implementation. However, it can be difficult to design a well-behaved macro! Additionally, compiler errors in macro code are harder to interpret, because they describe problems in the expanded code, not the source-level form that developers use.

These drawbacks make macros something of a "feature of last resort". That's not to say that macros are bad; they are part of Rust because sometimes they're needed for truly concise, well-abstracted code. Just keep this tradeoff in mind.

Defining a macro

You may have seen the vec! macro, used to initialize a vector with any number of elements.

let x: Vec<u32> = vec![1, 2, 3];

This can't be an ordinary function, because it takes any number of arguments. But we can imagine it as syntactic shorthand for

let x: Vec<u32> = {
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
};

We can implement this shorthand, using a macro: 1

macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

Whoa, that's a lot of new syntax! Let's break it down.

macro_rules! vec { ... }

This says we're defining a macro named vec, much as fn vec would define a function named vec. In prose, we informally write a macro's name with an exclamation point, e.g. vec!. The exclamation point is part of the invocation syntax and serves to distinguish a macro from an ordinary function.

Matching

The macro is defined through a series of rules, which are pattern-matching cases. Above, we had

( $( $x:expr ),* ) => { ... };

This is like a match expression arm, but the matching happens on Rust syntax trees, at compile time. The semicolon is optional on the last (here, only) case. The "pattern" on the left-hand side of => is known as a matcher. These have their own little grammar within the language.

The matcher $x:expr will match any Rust expression, binding that syntax tree to the metavariable $x. The identifier expr is a fragment specifier; the full possibilities are enumerated in the advanced macros chapter. Surrounding the matcher with $(...),* will match zero or more expressions, separated by commas.

Aside from the special matcher syntax, any Rust tokens that appear in a matcher must match exactly. For example,

macro_rules! foo {
    (x => $e:expr) => (println!("mode X: {}", $e));
    (y => $e:expr) => (println!("mode Y: {}", $e));
}

fn main() {
    foo!(y => 3);
}

will print

mode Y: 3

With

foo!(z => 3);

we get the compiler error

error: no rules expected the token `z`

Expansion

The right-hand side of a macro rule is ordinary Rust syntax, for the most part. But we can splice in bits of syntax captured by the matcher. From the original example:

$(
    temp_vec.push($x);
)*

Each matched expression $x will produce a single push statement in the macro expansion. The repetition in the expansion proceeds in "lockstep" with repetition in the matcher (more on this in a moment).

Because $x was already declared as matching an expression, we don't repeat :expr on the right-hand side. Also, we don't include a separating comma as part of the repetition operator. Instead, we have a terminating semicolon within the repeated block.

Another detail: the vec! macro has two pairs of braces on the right-hand side. They are often combined like so:

macro_rules! foo {
    () => {{
        ...
    }}
}

The outer braces are part of the syntax of macro_rules!. In fact, you can use () or [] instead. They simply delimit the right-hand side as a whole.

The inner braces are part of the expanded syntax. Remember, the vec! macro is used in an expression context. To write an expression with multiple statements, including let-bindings, we use a block. If your macro expands to a single expression, you don't need this extra layer of braces.

Note that we never declared that the macro produces an expression. In fact, this is not determined until we use the macro as an expression. With care, you can write a macro whose expansion works in several contexts. For example, shorthand for a data type could be valid as either an expression or a pattern.

Repetition

The repetition behavior can seem somewhat magical, especially when multiple names are bound at multiple nested levels of repetition. The two rules to keep in mind are:

  1. the behavior of $(...)* is to walk through one "layer" of repetitions, for all of the $names it contains, in lockstep, and
  2. each $name must be under at least as many $(...)*s as it was matched against. If it is under more, it'll be duplicated, as appropriate.

This baroque macro illustrates the duplication of variables from outer repetition levels.

macro_rules! o_O {
    (
        $(
            $x:expr; [ $( $y:expr ),* ]
        );*
    ) => {
        &[ $($( $x + $y ),*),* ]
    }
}

fn main() {
    let a: &[i32]
        = o_O!(10; [1, 2, 3];
               20; [4, 5, 6]);

    assert_eq!(a, [11, 12, 13, 24, 25, 26]);
}

That's most of the matcher syntax. These examples use $(...)*, which is a "zero or more" match. Alternatively you can write $(...)+ for a "one or more" match. Both forms optionally include a separator, which can be any token except + or *.

Hygiene

Some languages implement macros using simple text substitution, which leads to various problems. For example, this C program prints 13 instead of the expected 25.

#define FIVE_TIMES(x) 5 * x

int main() {
    printf("%d\n", FIVE_TIMES(2 + 3));
    return 0;
}

After expansion we have 5 * 2 + 3, and multiplication has greater precedence than addition. If you've used C macros a lot, you probably know the standard idioms for avoiding this problem, as well as five or six others. In Rust, we don't have to worry about it.

macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

fn main() {
    assert_eq!(25, five_times!(2 + 3));
}

The metavariable $x is parsed as a single expression node, and keeps its place in the syntax tree even after substitution.

Another common problem in macro systems is variable capture. Here's a C macro, using a GNU C extension to emulate Rust's expression blocks.

#define LOG(msg) ({ \
    int state = get_log_state(); \
    if (state > 0) { \
        printf("log(%d): %s\n", state, msg); \
    } \
})

This looks reasonable, but watch what happens in this example:

const char *state = "reticulating splines";
LOG(state);

The program will likely segfault, after it tries to execute

printf("log(%d): %s\n", state, state);

The equivalent Rust macro has the desired behavior.

macro_rules! log {
    ($msg:expr) => {{
        let state: i32 = get_log_state();
        if state > 0 {
            println!("log({}): {}", state, $msg);
        }
    }};
}

fn main() {
    let state: &str = "reticulating splines";
    log!(state);
}

This works because Rust has a hygienic macro system. Each macro expansion happens in a distinct syntax context, and each variable is tagged with the syntax context where it was introduced. It's as though the variable state inside main is painted a different "color" from the variable state inside the macro, and therefore they don't conflict.

This also restricts the ability of macros to introduce new bindings at the invocation site. Code such as the following will not work:

macro_rules! foo {
    () => (let x = 3);
}

fn main() {
    foo!();
    println!("{}", x);
}

Instead you need to pass the variable name into the invocation, so it's tagged with the right syntax context.

macro_rules! foo {
    ($v:ident) => (let $v = 3);
}

fn main() {
    foo!(x);
    println!("{}", x);
}

This holds for let bindings and loop labels, but not for items. So the following code does compile:

macro_rules! foo {
    () => (fn x() { });
}

fn main() {
    foo!();
    x();
}

Further reading

The advanced macros chapter goes into more detail about macro syntax. It also describes how to share macros between different modules or crates.


  1. The actual definition of vec! in libcollections differs from the one presented here, for reasons of efficiency and reusability. Some of these are mentioned in the advanced macros chapter