Rust Attribute & Function-Like Macros
Attribute macros and function-like macros are the two procedural macro kinds besides derives. An attribute macro rewrites a whole item, such as a function.
Split the function into parts
ItemFn gives you the pieces of the function. vis is the pub part. sig is the signature line. block holds the body:
let func = parse_macro_input!(item as ItemFn);
let vis = &func.vis;
let sig = &func.sig;
let name = &sig.ident;
let stmts = &func.block.stmts;Rebuild it with one line added
The new function keeps the old parts. One print goes first. The old body follows it:
quote! {
#vis #sig {
println!("calling {}", stringify!(#name));
#(#stmts)*
}
}The user writes one word
The attribute sits on the function. Every call now prints its name first:
#[log_call]
fn add(a: i32, b: i32) -> i32 {
a + b
}
println!("sum = {}", add(2, 3));A function-like macro parses a list
repeat_vec! takes a value and a count. A comma splits them. Punctuated reads a comma list of expressions:
let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
let args = match parser.parse2(stream) {
Ok(args) => args,
Err(err) => return err.into_compile_error().into(),
};
let value = &args[0];
let count = &args[1];The call looks like a function
It only looks like a call. The macro reads the two expressions at compile time. It writes a small loop in their place:
let copies = repeat_vec!("ab", 3);
println!("copies: {:?}", copies);Example
// toolkit holds both macros.
// Built and run with syn 2.0.119 (full feature), quote 1.0.47, proc-macro2 1.0.107.
use toolkit::{log_call, repeat_vec};
#[log_call]
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
println!("sum = {}", add(2, 3));
let copies = repeat_vec!("ab", 3);
println!("copies: {:?}", copies);
}Key points
- An attribute macro receives two streams
- The first holds the words inside its own brackets
- The second holds the whole item below it
- #[proc_macro] marks a function-like macro
- Its input is whatever sits between its brackets
- The input need not be valid Rust
- syn parses whole items with the full feature
- parse_macro_input!(item as ItemFn) reads a complete function
- An attribute macro returns the full replacement item
- Anything it leaves out is gone for good
- A pasted expression runs once per copy
- repeat_vec! evaluates its value on every push
- A function-like macro parses its own little language.
In this example, two kinds of macro work side by side. The attribute macro reads the add function at compile time. It rebuilds the function with a print as the first line. So the call prints calling add. Then main prints the sum. The function-like macro reads its two expressions. One is the text "ab". The other is the count 3. It writes a block with a small loop. The loop pushes the value three times. Main prints the three copies. Neither macro leaves any trace at run time.
Note
Attribute arguments arrive as raw tokens
Macros like #[route(GET, "/")] parse those tokens themselves
They often parse them into a small struct
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What does an attribute macro receive?