Rust Procedural Macros
A procedural macro is an ordinary Rust function that runs inside the compiler and writes code. A procedural macro can read field names and parse a small language.
Derive: the compiler writes the impl
This kind runs from the beginning: #[derive(Name)] on a struct or enum adds an impl for it. The next lesson builds one:
#[derive(Debug)]
struct Point { x: i32, y: i32 }
println!("{:?}", Point { x: 1, y: 2 });Attribute: rewrites the item below it
An attribute macro can sit on any item, such as a function, and rewrite the whole item. A web framework's #[route(GET, "/")] works this way. So does this one. It rebuilds an async main into a normal fn that starts a runtime:
#[tokio::main]
async fn main() {
// your async code — the macro rebuilds main around it
}Function-like: any language inside
This only looks like a function call. The tokens inside may be any language at all — a macro like this can parse and check SQL while compiling:
let query = sql!(SELECT name FROM users WHERE id = 1);
// not a call: a macro reads the SQL tokens at compile timeA proc macro is a Rust function
It takes a TokenStream and returns a TokenStream. It must live in its own crate, with proc-macro = true in its Cargo.toml — an ordinary crate cannot define one:
#[proc_macro_derive(Hello)]
pub fn derive_hello(input: TokenStream) -> TokenStream {
let parsed = syn::parse(input).unwrap();
build_impl(&parsed).into()
}Example
#[derive(Debug, Clone)]
struct Point { x: i32, y: i32 }
fn main() {
let p = Point { x: 1, y: 2 };
println!("derive made Debug: {:?}", p);
println!("attribute macros rewrite an item, like #[tokio::main]");
println!("function-like macros look like calls, like sql!(...)");
}Key points
- TokenStream: your code chopped into its smallest pieces — names, numbers, text, and punctuation marks
- The macro runs while compiling
- Its returned tokens are added to (or replace) your code
- At run time, no trace of it remains
- One crate may hold several procedural macros, of the same or different kinds
- A declarative macro only rearranges pieces it matched, like a stencil.
In this example, the first line is a procedural macro at work. #[derive(Debug, Clone)] asks the compiler to generate Debug and Clone impls for Point. That is code you never wrote by hand. Because Debug was generated, {:?} can print the whole point: Point { x: 1, y: 2 }. The last two lines simply name the other two kinds, so you can recognise them in real code. One is an attribute macro such as #[tokio::main], rewriting the function below it. The other is a function-like macro such as sql!(...) that only looks like a call.
Note
You will use procedural macros far more often than you write them
serde, Tokio and most web frameworks ship them
Writing one is a library author's job
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Where must a procedural macro be defined?