🎓 A1Learner
🔍

Rust Recursive Macros

A recursive macro is a macro that calls itself. Each call works on a smaller piece of the input.

Invented words as separators

and is not a Rust keyword here. It is a literal token the macro invented. It must appear in the call exactly as written. It also separates the two captures, so no comma is needed:

macro_rules! both {
    ($l:expr; and $r:expr) => { $l && $r };
}
println!("both = {}", both!(2 > 1; and 3 > 2));

Overloading: one macro, several shapes

One macro with several arms for several shapes is called overloading. The arms are tried in order, like match:

macro_rules! size_of_list {
    () => { 0 };
    ($x:expr) => { 1 };
    ($x:expr, $y:expr) => { 2 };
}
println!("{} {} {}", size_of_list!(), size_of_list!(9), size_of_list!(9, 9));

Base case first, then the recursive arm

The base arm handles one value directly, with no further call. The recursive arm splits the list into the first piece and the rest. The + means the rest holds at least one piece. So the calls shrink until only the base case is left:

macro_rules! biggest {
    ($x:expr) => { $x };
    ($x:expr, $($y:expr),+) => { std::cmp::max($x, biggest!($($y),+)) };
}
println!("biggest = {}", biggest!(3, 9, 4));

A call that fits no arm

biggest! separates its values with commas. A semicolon fits no arm, and the error names the first unexpected token:

biggest!(3; 9);
// error: no rules expected `;`

The recursion limit

Recursion has a hard limit: the compiler stops after about 128 levels of expansion and reports "recursion limit reached". Raise it only when you must — at the very top of the crate, with the number written as a string:

// first line of the crate:
#![recursion_limit = "256"]

Example

macro_rules! check {
    ($l:expr; and $r:expr) => {
        println!("{} and {} is {}", stringify!($l), stringify!($r), $l && $r)
    };
    ($l:expr; or $r:expr) => {
        println!("{} or {} is {}", stringify!($l), stringify!($r), $l || $r)
    };
}
macro_rules! smallest {
    ($x:expr) => { $x };
    ($x:expr, $($y:expr),+) => {
        std::cmp::min($x, smallest!($($y),+))
    };
}
fn main() {
    check!(1 + 1 == 2; and 2 * 2 == 4);
    check!(true; or false);
    println!("smallest = {}", smallest!(5, 2 * 3, 4));
    println!("one = {}", smallest!(9));
}

Key points

  • Each arm ends with a semicolon inside macro_rules! — the last arm may omit it, but keeping it is clearer
  • One-token-at-a-time macros reach the limit fast.
  • A muncher over 140 tokens already needs a raised limit.
  • Treat hitting the limit as a design smell.
  • Munch bigger bites per step, or switch to plain repetition.
  • Do that before reaching for a bigger number
  • Recursion is how variadic macros often work under the hood.
  • Handle the first argument.

In this example, check is overloaded. Its first arm matches the word and between two expressions. It joins them with &&. Its second arm matches or and joins with ||. Neither word is a Rust keyword here — each is a literal token in the matcher. smallest shows recursion. With one value, the base arm returns it: one = 9. With three values, the recursive arm keeps the first, 5, and calls smallest! on the rest, which keeps 2 * 3 and calls smallest! on 4 alone. The base arm returns 4, and min folds the values back up: smallest = 4. The TT Munchers lesson shows one-token-at-a-time macros in full.

Note

Then hand the rest back to the same macro.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

In smallest!(5, 2 * 3, 4), which arm does the work?