Macros
Compile-time metaprogramming through declarative and procedural macros.
Macros vs Functions
Macros: Variable parameters, operate on tokens, generate code at compile-time Functions: Fixed parameters, runtime execution, typed arguments
Declarative Macros (macro_rules!
)
Pattern match on code structure:
macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; }
Pattern syntax:
$x:expr
captures expressions$( ... ),*
captures repeated patterns with,
separator$()*
repeats code for each match
Procedural Macros
Transform TokenStream
→ TokenStream
. Three types:
1. Custom derive
Macros
#[derive(HelloMacro)] struct Pancakes; fn main() { Pancakes::hello_macro(); }
Implementation:
#[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_hello_macro(&ast) }
2. Attribute-like Macros
#[route(GET, "/")] fn index() {}
Signature:
#[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream
3. Function-like Macros
let sql = sql!(SELECT * FROM posts WHERE id=1);
Signature:
#[proc_macro] pub fn sql(input: TokenStream) -> TokenStream
Key Crates
syn
: Parse Rust code into ASTquote
: Generate Rust code from templatesproc_macro
: Compiler API for token manipulation
Practical Applications
Declarative: Domain-specific syntax, boilerplate reduction, type-safe builders Procedural: ORM derive macros, framework attributes, compile-time validation
Performance: All expansion happens at compile-time with zero runtime cost.