Rust Macros Cheat Sheet
Covers declarative macros with macro_rules!, common built-in macros, and the basics of derive and procedural macros in Rust.
macro_rules! Basics
Declaring a simple declarative macro with pattern matching.
// A simple declarative macromacro_rules! square { ($x:expr) => { $x * $x };}fn main() { let result = square!(5); // expands to 5 * 5 println!("{}", result);}// Macros can have multiple match arms, like matchmacro_rules! greet { () => { println!("Hello!"); }; ($name:expr) => { println!("Hello, {}!", $name); };}greet!(); // "Hello!"greet!("Ferris"); // "Hello, Ferris!"
Repetition Patterns
Matching a variable number of macro arguments.
// $(...),* repeats zero or more times, comma-separatedmacro_rules! my_vec { ( $( $x:expr ),* ) => { { let mut v = Vec::new(); $( v.push($x); )* v } };}let v = my_vec![1, 2, 3]; // expands to a Vec containing 1, 2, 3// $(...),+ requires one or more repetitionsmacro_rules! my_max { ($x:expr) => { $x }; ($x:expr, $( $rest:expr ),+) => { { let rest_max = my_max!($( $rest ),+); if $x > rest_max { $x } else { rest_max } } };}
Common Built-in Macros
Macros available from the standard library without any imports.
- println! / print!- Formatted output to stdout, with/without trailing newline
- format!- Builds a String using the same formatting syntax as println!
- vec!- Creates a Vec<T> from a list of elements or repeated value/count
- assert! / assert_eq! / assert_ne!- Runtime checks that panic on failure, used heavily in tests
- panic!- Triggers an unrecoverable error with a formatted message
- todo! / unimplemented!- Placeholder macros that panic when reached, useful during development
- matches!- Returns true/false for whether a value matches a given pattern
- dbg!- Prints a value with file/line info to stderr and returns it, for quick debugging
Derive Macros
Using #[derive(...)] to auto-generate trait implementations.
// #[derive(...)] is a procedural macro that generates trait impls at compile time#[derive(Debug, Clone, PartialEq)]struct Point { x: i32, y: i32,}// serde's derive macros (from the `serde` crate) generate serialization code#[derive(serde::Serialize, serde::Deserialize)]struct Config { name: String, retries: u32,}let p1 = Point { x: 1, y: 2 };let p2 = p1.clone();assert_eq!(p1, p2); // works because of #[derive(PartialEq)]println!("{:?}", p1); // works because of #[derive(Debug)]
Fragment Specifiers
Types of syntax a macro_rules! matcher can capture.
- expr- Matches an expression (e.g. 1 + 2, foo())
- ident- Matches an identifier (variable or function name)
- ty- Matches a type (e.g. Vec<i32>)
- block- Matches a brace-delimited block of statements
- pat- Matches a pattern (as used in match arms)
- stmt- Matches a single statement
- tt- Matches a single token tree; the most flexible/general specifier
- literal- Matches a literal value (e.g. "hi", 42, true)
Procedural Macro Crate Setup
Proc macros must live in their own crate with `proc-macro = true`, built on the syn/quote ecosystem.
// Cargo.toml for the proc-macro crate// [lib]// proc-macro = true//// [dependencies]// syn = { version = "2", features = ["full"] }// quote = "1"// proc-macro2 = "1"use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, DeriveInput};#[proc_macro_derive(Describe)]pub fn derive_describe(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let name = &ast.ident; let expanded = quote! { impl Describe for #name { fn describe() -> &'static str { stringify!(#name) } } }; expanded.into()}
Attribute & Function-like Proc Macros
The other two proc-macro flavors besides #[derive(...)], useful for wrapping items or generating code from custom syntax.
use proc_macro::TokenStream;use quote::quote;use syn::{parse_macro_input, ItemFn};// Attribute macro: #[my_timed] fn foo() { ... }#[proc_macro_attribute]pub fn my_timed(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemFn); let name = &input.sig.ident; let block = &input.block; let sig = &input.sig; quote! { #sig { let __start = std::time::Instant::now(); let __result = (|| #block)(); println!("{} took {:?}", stringify!(#name), __start.elapsed()); __result } }.into()}// Function-like: sql!("SELECT * FROM users") -- parses arbitrary custom syntax#[proc_macro]pub fn sql(input: TokenStream) -> TokenStream { let query = input.to_string(); quote! { format!("executing: {}", #query) }.into().into()}
TT-Munching & Recursive Declarative Macros
A common macro_rules! technique for parsing token trees one piece at a time.
// "tt muncher": peel off one token tree per recursive callmacro_rules! count_idents { () => { 0 }; ($head:ident $(, $tail:ident)*) => { 1 + count_idents!($( $tail ),*) };}let n = count_idents!(a, b, c); // 3, computed entirely at compile time// Internal rules (prefixed with @) keep helper arms out of the public APImacro_rules! build_map { (@insert $map:ident, $k:expr => $v:expr) => { $map.insert($k, $v); }; ( $( $k:expr => $v:expr ),* $(,)? ) => {{ let mut m = std::collections::HashMap::new(); $( build_map!(@insert m, $k => $v); )* m }};}let scores = build_map!{ "a" => 1, "b" => 2 };
Macro Hygiene & Exporting
How identifiers introduced by a macro avoid clashing with the caller's scope, and how to publish a macro from a crate.
// Hygiene: `tmp` inside the macro can't collide with a caller's own `tmp`macro_rules! swap { ($a:expr, $b:expr) => {{ let tmp = $a; $a = $b; $b = tmp; }};}fn main() { let mut tmp = 1; // no clash with the macro's internal `tmp` let mut y = 2; swap!(tmp, y);}// #[macro_export] makes a macro_rules! macro visible outside its defining crate,// and places it at the crate root so callers do `use my_crate::my_macro;`#[macro_export]macro_rules! my_macro { () => { println!("from my_crate") };}
Procedural Macro Vocabulary
Terms you'll hit once you move from macro_rules! to real proc macros.
- TokenStream- The compiler-facing sequence of tokens a proc macro receives and returns; proc_macro2's version is usable outside the compiler for testing
- syn- Parses a TokenStream into a structured Rust AST (DeriveInput, ItemFn, Expr, ...)
- quote!- Macro that turns Rust syntax back into a TokenStream, with #var interpolation
- Span- Source-location metadata attached to tokens, used for accurate compiler error messages
- Derive helper attribute- An extra attribute (e.g. #[serde(rename = ...)]) a derive macro is allowed to also parse off the input
- cargo expand- Third-party cargo subcommand that prints the fully macro-expanded source, essential for debugging both macro_rules! and proc macros
- Incremental TokenStream errors- syn::Error::to_compile_error() converts parse failures into a proper compiler diagnostic instead of a panic
Reach for a declarative macro_rules! macro first — it's far simpler to write and debug than a procedural macro. Only build a proc macro (in its own crate with proc-macro = true) when you need to generate code from arbitrary Rust syntax, like a custom #[derive(...)].