🎓 A1Learner
🔍
0 of 149 lessons completed

Rust Macro Callbacks

A macro callback is a pattern where one macro calls another macro by name. Nesting calls does not work, because arguments are matched before any call inside them expands.

Nesting does not do what you expect

The outer macro never sees the word the inner macro would have produced. It sees the unexpanded call itself:

// The outer macro receives the tokens: expand!()
// It does NOT receive whatever expand!() would make.
recognize!(expand!())

Ask for the next macro by name

Capture a macro name as an ident, then call it with tokens you choose:

($callback:ident) => { $callback!(banana) };

Forward any arguments untouched

Wrap the callback call in the input, and pass its whole token group through:

($callback:ident( $($args:tt)* )) => {
    $callback!( $($args)* )
};

Example

macro_rules! as_is {
    ($($t:tt)*) => { println!("plain: {}", stringify!($($t)*)) };
}
macro_rules! upper {
    ($($t:tt)*) => { println!("upper: {}", stringify!($($t)*).to_uppercase()) };
}
macro_rules! greet_with {
    ($callback:ident) => { $callback!(hello rust) };
}
fn main() {
    greet_with!(as_is);
    greet_with!(upper);
}

Key points

  • A macro's arguments are matched before any macro call inside them is expanded
  • An outer macro sees a nested call as raw tokens
  • It never sees the call's result
  • Callback pattern: capture the name of another macro as $callback:ident, then invoke $callback!(...) in the expansion
  • The first macro decides when the callback runs
  • It also decides what tokens the callback receives
  • The caller decides which macro it is
  • Forward whole argument lists untouched by capturing them as $($args:tt)* and passing them on inside the callback call
  • The callback macro must be in scope where the outer macro is called
  • Its name is read at the call site

In this example, greet_with never prints anything itself. It captures the name of a macro as $callback, then calls that macro with the tokens hello rust. The first call in main passes as_is, so the tokens go to as_is, which prints them plainly. The second passes upper, and the same tokens are printed in capitals. greet_with fixed the words. main chose the treatment. That split of jobs is the callback pattern.

Note

Callbacks pair naturally with munchers and push-down accumulation

The worker macro parses

The callback macro decides what the parsed pieces mean

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why use a callback macro instead of nesting one macro call inside another's arguments?