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?