Macros are Rust’s compile-time code generation mechanism. Declarative macros are like sophisticated find-and-replace operating on token trees, enabling variadic functions and syntax sugar that the type system cannot express. Procedural macros are Rust programs that transform code, with full access to the abstract syntax tree. They power derive macros that automatically generate trait implementations, eliminating boilerplate while maintaining type safety. Macros trade readability for expressiveness, and should be used when functions and generics are insufficient.
Macros in Rust are a metaprogramming mechanism that operates on the abstract syntax tree (AST) Rather than on values. They expand at compile time, transforming token sequences into new token Sequences that the compiler then processes as ordinary Rust code.
Rust macros solve problems that the type system and generics cannot:
Variadic functions : println! accepts any number of arguments of any type. A generic function cannot express “zero or more arguments, each implementing Display” with a single signature.Syntax extension : Macros can introduce new syntactic forms (pattern matching on token trees) that are not representable as function calls. vec![1, 2, 3] is syntactic sugar that would be impossible as a plain function.Code generation : Macros eliminate boilerplate by generating repetitive code at compile time. Derive macros (#[derive(Clone)]) generate impl blocks that would be tedious and error-prone to write by hand.Domain-specific languages : Macros can parse custom syntax within their delimiters, enabling embedded DSLs like sql!(SELECT * FROM users) or route!(GET /users -> list_users).Mechanism Operates on Evaluated at When to use Function Values Runtime Logic on concrete values Generic function Types (monomorphized) Compile time Logic parameterized by type Trait Behavior contracts Compile time Shared behavior across types Declarative macro Token trees Compile time Pattern-matching on syntax, variable-arity Proc macro AST / TokenStream Compile time Code generation requiring full type information
Rust has two fundamentally different macro systems:
Declarative macros (macro_rules!): Pattern-match on token trees and produce new token trees. They are hygienic (variables in the macro cannot capture variables in the call site) but cannot inspect types or perform complex AST manipulation.
Procedural macros : Rust functions that take a TokenStream and return a TokenStream. They have full access to the token stream and, via the syn crate, to the parsed AST. They come in three flavors: derive, attribute-like, and function-like.
A declarative macro is defined with macro_rules! and consists of one or more arms , each with a Pattern and an expansion:
println! ( "Hello, world!" );
Metavariables capture parts of the input and make them available in the expansion. They are prefixed With $ and annotated with a fragment specifier that constrains what tokens they match:
macro_rules! create_function {
println! ( "Called function: {}" , stringify! ( $ func_name));
create_function! (my_func);
Specifier Matches Example input identIdentifier foo``MyType``_tmptyType expression i32``Vec<String>``&[u8]exprExpression 1 + 2``foo()``x * ypathPath (module path or type path) std::collections::HashMapstmtStatement (without trailing semicolon) let x = 5blockBlock (braced statements) { let x = 1; x + 2 }patPattern Some(x)``_``1..=100literalLiteral (string, number, char, bool) "hello"``42``truemetaMeta attribute (inner content of #[...]) derive(Debug, Clone)itemItem (function, struct, impl, etc.) fn foo() {}``struct S;visVisibility modifier pub``pub(crate)(none)lifetimeLifetime "a``'staticttToken tree (single token or matched delimiters) =>``(a, b)``[]
The tt specifier is the most flexible — it matches any single token or any pair of matched Delimiters (parentheses, brackets, or braces). It is the “wildcard” of fragment specifiers.
Declarative macros support repetition with the syntax $( ... ) sep rep where sep is an optional Separator and rep is one of:
* — zero or more repetitions+ — one or more repetitions? — zero or one repetitionmacro_rules! count_args {
( $ first : tt $ (, $ rest : tt) * ) => {
1 usize + count_args! ( $ ( $ rest), * )
assert_eq! ( count_args! (), 0 );
assert_eq! ( count_args! (a), 1 );
assert_eq! ( count_args! (a, b, c), 3 );
The separator can be any token. Common separators are , (comma) and ; (semicolon):
( $ ( $ elem : expr), * $ (,) ? ) => {
let v = vector! [ 1 , 2 , 3 ];
let v2 = vector! [ 4 , 5 , 6 ,];
The trailing $(,)? makes the trailing comma optional, which is idiomatic Rust style.
Repetitions can be nested. The expansion repeats the innermost repetition completely for each Iteration of the outer one:
( $ ([ $ ( $ elem : expr), + ]), + $ (,) ? ) => {
assert_eq! (m[ 0 ], vec! [ 1 , 2 , 3 ]);
assert_eq! (m[ 1 ], vec! [ 4 , 5 , 6 ]);
A macro arm can invoke itself. This is the primary mechanism for processing variable-length input:
( $ x : expr, $ ( $ rest : expr), + ) => {
let rest_min = find_min! ( $ ( $ rest), + );
if $ x < rest_min { $ x } else { rest_min }
assert_eq! ( find_min! ( 3 , 1 , 4 , 1 , 5 ), 1 );
Will produce the error "recursion limit reached while expanding ...". Use `#![recursion_limit = "256"]` at the crate root to increase the limit if needed. For truly complex Processing, use a procedural macro instead.The stringify! macro converts a token sequence to a string literal at compile time. This is useful For debug output and identifiers:
println! ( "{} = {:?}" , stringify! ( $ val), $ val);
Declarative macros can use the @ internal rule set for recursive descent parsing. This is a Pattern where you define a set of internal rules with the syntax @<name> and delegate to them:
macro_rules! impl_method {
( $ name : ident, $ ret : ty, $ ( $ body : tt) * ) => {
impl_method! ( @ parse $ ( $ body) * )
// Internal rule: return a literal
( @ parse return $ val : expr) => { $ val };
// Internal rule: compute and return
( @ parse $ a : expr; $ b : expr; return $ c : expr) => {
{ let _ = $ a; let _ = $ b; $ c }
impl_method! (get_value, i32 , return 42 )
impl_method! (compute, i32 , 1 + 1 ; 2 + 2 ; return 99 )
The standard library vec! macro demonstrates several techniques:
( $ elem : expr; $ n : expr) => {
std :: vec :: from_elem ( $ elem, $ n)
( $ ( $ x : expr), + $ (,) ? ) => {
<[_]> :: into_vec ( Box :: new ([ $ ( $ x), + ]))
The $elem:expr; $n:expr arm creates a vector of $n copies of $elem. The $($x:expr),+ arm Creates a vector from a list of expressions. The () => arm creates an empty vector.
$crate:: io :: _print ( std :: format_args! ( "" ))
$crate:: io :: _print ( std :: format_args! ( $ ( $ arg) * ))
The key insight is that println! delegates to format_args!Which is a built-in compiler macro That parses the format string at compile time and validates the arguments. The $($arg:tt)* pattern Captures everything as token trees and passes them through to format_args!.
( $ left : expr, $ right : expr $ (,) ? ) => ({
match ( &$ left, &$ right) {
(left_val, right_val) => {
if ! ( * left_val == * right_val) {
"assertion `left == right` failed \n left: {:?} \n right: {:?}" ,
( $ left : expr, $ right : expr, $ ( $ arg : tt) + ) => ({
match ( &$ left, &$ right) {
(left_val, right_val) => {
if ! ( * left_val == * right_val) {
"assertion `left == right` failed \n left: {:?} \n right: {:?}: {}" ,
left_val, right_val, std :: format_args! ( $ ( $ arg) + )
Note the use of &$left and &$right inside a match. Borrowing the arguments ensures they are Only evaluated once, which matters when the expressions have side effects. This is a critical Pattern for any macro that inspects its arguments.
By default, a macro_rules! macro defined in a module is only visible within that module. Use #[macro_export] to make the macro available at the crate root:
// In my_macros/src/lib.rs
panic! ( "assertion failed: {}" , stringify! ( $ cond));
Definition appears. This means a macro defined in a submodule is accessible as `my_crate::my_assert!`Not `my_crate::submodule::my_assert!`. This is a historical design decision That has no prospect of changing.Macro scoping in Rust has changed over editions. As of edition 2021:
A macro_rules! macro without #[macro_export] is scoped to the module it is defined in. A #[macro_export] macro is always placed at the crate root and is available to downstream crates. Macros can be imported with use my_crate::macro_name; (note: the ! is omitted in use paths). The $crate metavariable expands to the path of the defining crate, ensuring that paths inside the macro expansion refer to items in the macro’s crate, not the calling crate. ( $ ( $ key : expr => $ val : expr), + $ (,) ? ) => {
let mut m = $crate:: HashMap :: new ();
No type information : Declarative macros operate on token trees, not on typed AST nodes. You cannot inspect whether an expression has type i32 or String inside a macro_rules! expansion.
Pattern matching is syntactic, not semantic : The ty fragment specifier matches tokens that look like a type, but it cannot distinguish between a type that exists and one that does not. A misspelled type name will pass through the macro and only fail later during type checking.
No control flow : You cannot use if/else or loops inside a macro_rules! expansion. All “branching” is done through pattern matching on the input.
Error messages are poor : When a macro expansion fails, the error points to the macro invocation site, not to the specific arm or token that caused the failure. Complex macros with many arms produce cryptic error messages.
Recursion limit : Deeply recursive macros hit the recursion limit and are not suitable for processing large inputs.
Declarative macros are hygienic : identifiers introduced by the macro cannot capture identifiers From the surrounding scope, and identifiers from the surrounding scope cannot accidentally be used Inside the macro.
This code will not compile . The x inside the macro expansion is in the macro’s hygiene Context, which is separate from the call site’s x. To intentionally use a variable from the call Site, pass it as a metavariable:
The expr fragment captures the token x from the call site, preserving its hygiene context.
Procedural macros are Rust functions that transform a TokenStream into another TokenStream. They Run as a separate compilation unit (compiled to a dynamic library loaded by the compiler), which Means they can use arbitrary Rust code for parsing and generation.
Type Syntax Applied to Derive #[derive(MyTrait)]Struct, enum, union Attribute-like #[my_attr(args)]Any item (fn, struct, etc.) Function-like my_macro!(tokens)Arbitrary token sequences
A procedural macro crate has a specific Cargo.toml configuration:
syn = { version = " 2 " , features = [ " full " , " parsing " , " extra-traits " ] }
The proc-macro = true key tells Cargo this crate produces a proc-macro library. The crate cannot Export anything other than proc-macro entry points.
The typical workspace layout separates the proc-macro crate from the consumer crate:
Cargo.toml # proc-macro = true
Cargo.toml # depends on my_macros
members = [ " my_macros " , " my_app " ]
my_macros = { path = " ../my_macros " }
proc-macro2 : A wrapper around the compiler’s proc_macro::TokenStream that implements Clone``DebugAnd other standard traits. Use proc_macro2::TokenStream everywhere in your code; convert to/from proc_macro::TokenStream only at the entry point.
syn : A complete Rust parser. Parses TokenStream into typed AST nodes ( structs, functions, expressions, types, etc.). Supports #[derive] parsing, attribute parsing, and all of Rust syntax.
quote : A macro for constructing TokenStream values from Rust syntax. The inverse of syn: you write normal Rust code inside quote!And it produces a TokenStream.
Definition. A derive macro generates trait implementations for a struct, enum, or union. It is Invoked with #[derive(AttributeName)] and the derive macro function receives the type definition As a TokenStream.
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, DeriveInput };
#[proc_macro_derive( Inspect )]
pub fn derive_inspect (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as DeriveInput );
fn inspect ( & self ) -> String {
format! ( "{}: {:?}" , stringify! (#name), self )
TokenStream :: from (expanded)
fn inspect ( & self ) -> String ;
let p = Point { x : 1 , y : 2 };
println! ( "{}" , p . inspect ());
Derive macros can declare helper attributes that appear on fields or variants. These are parsed from The input and used to customize the generated implementation:
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, DeriveInput , Data , Fields , Attribute , Meta , Lit , NestedMeta };
#[proc_macro_derive( Builder , attributes(builder))]
pub fn derive_builder (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as DeriveInput );
let fields = match & input . data {
Data :: Struct (data) => match & data . fields {
Fields :: Named (fields) => & fields . named,
_ => panic! ( "Builder only supports structs with named fields" ),
_ => panic! ( "Builder only supports structs" ),
let builder_name = quote! { #nameBuilder };
let builder_fields : Vec <_> = fields . iter () . map ( | f | {
quote! { #name : Option <#ty> }
let builder_inits : Vec <_> = fields . iter () . map ( | f | {
let builder_setters : Vec <_> = fields . iter () . map ( | f | {
fn # name ( mut self , value : #ty) -> Self {
self . #name = Some (value);
let builder_build_fields : Vec <_> = fields . iter () . map ( | f | {
#name : self . #name . ok_or ( concat! (
"field '" , stringify! (#name), "' is required"
pub struct #builder_name {
pub fn builder () -> #builder_name {
pub fn build ( self ) -> Result <#name, String > {
#(#builder_build_fields), *
TokenStream :: from (expanded)
let user = User :: builder ()
. name ( "Alice" . to_string ())
. email ( "alice@example.com" . to_string ())
Helper attributes are accessible via syn. The #[builder(each = "name")] pattern lets you Customize the setter for Vec<T> fields:
use syn :: { Attribute , Meta , Expr , ExprLit , Lit };
fn get_each_attr (attr : & Attribute ) -> Option < String > {
if attr . path () . is_ident ( "builder" ) {
attr . parse_args_with ( | input : syn :: parse :: ParseStream | {
let ident : syn :: Ident = input . parse () ? ;
let _eq : Token! [ = ] = input . parse () ? ;
let lit : syn :: LitStr = input . parse () ? ;
A derive macro that generates a Clone implementation with a log message:
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, DeriveInput , Data , Fields };
#[proc_macro_derive( TrackedClone )]
pub fn derive_tracked_clone (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as DeriveInput );
let clone_body = match & input . data {
Fields :: Named (fields) => {
let field_clones : Vec <_> = fields . named . iter () . map ( | f | {
quote! { #name : self . #name . clone () }
quote! { Self { #(#field_clones), * } }
Fields :: Unnamed (fields) => {
let field_clones : Vec <_> = fields . unnamed . iter () . map ( | _ | {
quote! { Self ( #(#field_clones), * ) }
Fields :: Unit => quote! { Self },
let arms : Vec <_> = data . variants . iter () . map ( | v | {
Fields :: Named (fields) => {
let names : Vec <_> = fields . named . iter () . map ( | f | & f . ident) . collect ();
let clones : Vec <_> = names . iter () . map ( | n | {
quote! { #n : #n . clone () }
Self :: #ident { #(#names), * } => {
eprintln! ( "[TrackedClone] cloning {}::{}" , stringify! (#name), stringify! (#ident));
Self :: #ident { #(#clones), * }
Fields :: Unnamed (fields) => {
let bindings : Vec <_> = fields . unnamed . iter () . enumerate () . map ( | (i, _) | {
syn :: Ident :: new ( & format! ( "field_{}" , i), proc_macro2 :: Span :: call_site ())
let clones : Vec <_> = bindings . iter () . map ( | b | {
Self :: # ident ( #(#bindings), * ) => {
eprintln! ( "[TrackedClone] cloning {}::{}" , stringify! (#name), stringify! (#ident));
Self :: # ident ( #(#clones), * )
eprintln! ( "[TrackedClone] cloning {}::{}" , stringify! (#name), stringify! (#ident));
quote! { match self { #(#arms), * } }
return syn :: Error :: new_spanned (
"TrackedClone cannot be derived for unions"
) . to_compile_error () . into ();
fn clone ( & self ) -> Self {
TokenStream :: from (expanded)
Definition. An attribute-like macro is applied to an item (function, struct, impl block, etc.) With the syntax #[attr_name(args)] and replaces or augments the item. Unlike derive macros, it can Modify the item arbitrarily.
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, parse_quote, ItemFn , AttributeArgs };
pub fn trace (attr : TokenStream , item : TokenStream ) -> TokenStream {
let _args = parse_macro_input! (attr as AttributeArgs );
let mut func = parse_macro_input! (item as ItemFn );
let func_name = & func . sig . ident;
let func_args = & func . sig . inputs;
func . block = parse_quote! {
eprintln! ( "[TRACE] entering {}" , stringify! (#func_name));
eprintln! ( "[TRACE] exiting {}" , stringify! (#func_name));
TokenStream :: from ( quote! { #func })
fn add (a : i32 , b : i32 ) -> i32 {
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, parse_quote, ItemFn , LitStr , parse :: Parse , parse :: ParseStream , Token };
impl Parse for TraceArgs {
fn parse (input : ParseStream ) -> syn :: Result < Self > {
let level : LitStr = input . parse () ? ;
Ok ( TraceArgs { level : level . value () })
pub fn logged (attr : TokenStream , item : TokenStream ) -> TokenStream {
let args = parse_macro_input! (attr as TraceArgs );
let mut func = parse_macro_input! (item as ItemFn );
let func_name = & func . sig . ident;
func . block = parse_quote! {
log :: log! ( :: log :: Level :: #level, "entering {}" , stringify! (#func_name));
log :: log! ( :: log :: Level :: #level, "exiting {}" , stringify! (#func_name));
TokenStream :: from ( quote! { #func })
fn process_data (data : & str ) {
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, parse_quote, ItemFn , LitStr , Token , Ident , braced};
impl syn :: parse :: Parse for RouteArgs {
fn parse (input : syn :: parse :: ParseStream ) -> syn :: Result < Self > {
let method : Ident = input . parse () ? ;
let _comma : Token! [,] = input . parse () ? ;
let path : LitStr = input . parse () ? ;
method : method . to_string (),
pub fn route (attr : TokenStream , item : TokenStream ) -> TokenStream {
let args = parse_macro_input! (attr as RouteArgs );
let mut func = parse_macro_input! (item as ItemFn );
let func_name = & func . sig . ident;
let method = & args . method;
let route_fn_name = syn :: Ident :: new (
& format! ( "__route_meta_{}" , func_name),
proc_macro2 :: Span :: call_site (),
let registration = quote! {
pub fn # route_fn_name () -> ( & ' static str , & ' static str , fn ( /* handler args */ )) {
(#method, #path, #func_name)
TokenStream :: from (expanded)
#[route( GET , "/users/{id}" )]
fn get_user (id : u32 ) -> String {
Definition. A function-like procedural macro is invoked with macro_name!(tokens) syntax and Replaces the entire invocation with the generated code. Unlike macro_rules!It has full access to The token stream and can parse it with syn.
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, parse :: Parse , parse :: ParseStream , Ident , Token , LitInt , Result };
body : proc_macro2 :: TokenStream ,
impl Parse for SeqInput {
fn parse (input : ParseStream ) -> Result < Self > {
let var : Ident = input . parse () ? ;
let _in_token : Ident = input . parse () ? ;
return Err ( syn :: Error :: new (_in_token . span (), "expected 'in'" ));
let from : LitInt = input . parse () ? ;
let _dots : Token! [ .. ] = input . parse () ? ;
let to : LitInt = input . parse () ? ;
syn :: braced! (content in input);
let body : proc_macro2 :: TokenStream = content . parse () ? ;
from : from . base10_parse () ? ,
pub fn seq (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as SeqInput );
let mut expansions = Vec :: new ();
for i in input . from ..= input . to {
let lit = syn :: LitInt :: new ( & i . to_string (), proc_macro2 :: Span :: call_site ());
TokenStream :: from ( quote! {
println! ( "iteration {}" , i);
use proc_macro :: TokenStream ;
use syn :: {parse_macro_input, LitStr , Result , Token , Ident , braced};
impl syn :: parse :: Parse for SqlInput {
fn parse (input : syn :: parse :: ParseStream ) -> Result < Self > {
let query : LitStr = input . parse () ? ;
let mut params = Vec :: new ();
while input . peek ( Token! [,]) {
let _comma : Token! [,] = input . parse () ? ;
params . push (input . parse :: < Ident >() ? );
pub fn sql (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as SqlInput );
let query = & input . query;
let params = & input . params;
let param_count = params . len ();
let param_refs : Vec <_> = params . iter () . map ( | p | {
static SQL : & str = #query;
let params : [ & ( dyn std :: any :: Any + std :: marker :: Sync + std :: marker :: Send ); #param_count] = [
TokenStream :: from (expanded)
let (query, params) = sql! ( "SELECT * FROM users WHERE id = $1 AND name = $2" , user_id, name);
Validate parameter bindings at compile time, and generate type-safe code. Libraries like `sqlx` with Its `query!` macro provide this level of sophistication.The syn crate parses token streams into strongly-typed AST nodes. The primary entry points are:
use syn :: {parse2, parse_str, File , ItemFn , ItemStruct , Expr , Type };
use proc_macro2 :: TokenStream ;
let file : File = parse_str ( r#"
fn main() { println!("hello"); }
// Parse from a TokenStream
let tokens : TokenStream = quote! { struct Foo { x : i32 } };
let item : ItemStruct = parse2 (tokens) ? ;
// Parse a single expression
let expr : Expr = parse_str ( "1 + 2" ) ? ;
let ty : Type = parse_str ( "Vec<HashMap<String, i32>>" ) ? ;
The parse_macro_input! macro is a convenience wrapper used in proc-macro entry points:
let input = parse_macro_input! (input as DeriveInput );
It automatically converts proc_macro::TokenStream to proc_macro2::TokenStreamParses it, and Emits a compile error on parse failure.
Type Represents DeriveInputA struct, enum, or union (for derive macros) ItemFnA function definition ItemStructA struct definition ItemEnumAn enum definition ItemImplAn impl block ItemTraitA trait definition ItemModA module declaration ItemUseA use statement ExprAn expression (enum with many variants) TypeA type (enum with many variants) PatA pattern StmtA statement LitA literal (string, number, etc.) IdentAn identifier MetaA meta attribute (inner content of #[...]) AttributeA complete attribute including # and path Visibilitypub``pub(crate)Or inheritedSignatureA function signature FieldsNamed, unnamed, or unit fields of a struct VariantAn enum variant GenericParamA generic type or lifetime parameter WhereClauseA where clause PathA path like std::collections::HashMap
use syn :: { Data , Fields , DeriveInput };
fn process_struct (input : & DeriveInput ) {
if let Data :: Struct (data) = & input . data {
if let Fields :: Named (fields) = & data . fields {
for field in & fields . named {
let attrs = & field . attrs;
use syn :: { Data , Fields , Variant };
fn process_enum (input : & DeriveInput ) {
if let Data :: Enum (data) = & input . data {
for variant in & data . variants {
let name = & variant . ident;
let fields = & variant . fields;
let attrs = & variant . attrs;
// Process each variant...
The quote! macro converts Rust syntax into a proc_macro2::TokenStream:
let name = syn :: Ident :: new ( "MyStruct" , proc_macro2 :: Span :: call_site ());
let field_name = syn :: Ident :: new ( "x" , proc_macro2 :: Span :: call_site ());
let field_type : syn :: Type = syn :: parse_quote! ( i32 );
#field_name : #field_type,
fn new (#field_name : #field_type) -> Self {
Inside quote!Variables prefixed with # are interpolated:
#ident inserts an identifier#expr inserts an expression (anything that implements ToTokens)#(#items)* inserts a repetition (like $($items)* in macro_rules!)#(#items),* inserts a comma-separated repetition##ident pastes two identifiers (for generating unique names)quote_spanned! associates generated tokens with a specific span from the input. This ensures that Error messages point to the correct location in the user’s code:
use quote :: quote_spanned;
let span = field . ident . span ();
let field_name = & field . ident;
let setter = quote_spanned! {span =>
fn # field_name ( self , value : Value ) -> Self {
self . #field_name = Some (value);
Proc macros return errors by converting syn::Error into a TokenStream that contains compile_error! invocations:
use syn :: { Error , Result };
fn my_macro (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as DeriveInput );
if let Data :: Union (_) = & input . data {
let err = Error :: new_spanned (
"this macro cannot be derived for unions"
return err . to_compile_error () . into ();
Multiple errors can be collected with syn::Error::combine:
use syn :: { Error , Result };
fn validate_fields (fields : & FieldsNamed ) -> Result <()> {
let mut errors = Vec :: new ();
for field in & fields . named {
if let Some (attr) = field . attrs . iter () . find ( | a | a . path () . is_ident ( "invalid" )) {
errors . push ( Error :: new_spanned (attr, "invalid attribute on field" ));
Err (errors . into_iter () . reduce ( Error :: combine) . unwrap ())
The quote::ToTokens trait is implemented by all syn types. It converts an AST node into tokens That can be interpolated into a quote! block. You can implement ToTokens for your own types:
use proc_macro2 :: TokenStream ;
impl ToTokens for MyNode {
fn to_tokens ( & self , tokens : &mut TokenStream ) {
const #name : i32 = #value;
Rust macros are hygienic at the level of identifiers and lifetimes . This means:
Local variables : A variable introduced by a macro cannot conflict with a variable at the call site. They exist in different “syntax contexts.”
Paths : $crate is the only way to reference the macro’s crate from within the expansion. A bare use or super:: refers to the call site’s module hierarchy.
Proc macros are NOT fully hygienic : Procedural macros generate raw token streams. They do not automatically get the hygiene that macro_rules! provides. A proc macro that generates a local variable let x = ... could shadow a variable x at the call site.
// This proc macro is NOT hygienic
pub fn unsafe_local (input : TokenStream ) -> TokenStream {
TokenStream :: from ( quote! {
// The expansion could shadow the caller's x
let y = unsafe_local! (); // x is now 42, the original x is shadowed
To work around this, use unique variable names with paste! or ## (hash-hash) paste syntax:
let unique_name = syn :: Ident :: new (
& format! ( "__{}_inner" , func . sig . ident),
The cargo-expand tool shows the full macro expansion of your crate:
cargo install cargo-expand
cargo expand --lib my_module
This is the single most useful tool for debugging macros. It shows exactly what the compiler sees After all macro expansions.
The trace_macros! built-in macro prints each macro expansion to stdout during compilation:
#![feature(trace_macros)]
This requires the trace_macros nightly feature and is primarily useful for understanding how macro_rules! expansions proceed step by step.
Another nightly-only macro that prints token trees during expansion:
macro_rules! debug_print {
Derive macros are commonly used to generate builder patterns. The key decisions:
Should the builder use Option<T> for required fields or separate required/optional handling? Should setters take ownership or borrow? Should the builder be generic over error types? The most ergonomic pattern uses Option<T> for all fields and returns Result<T, E> from build(). Required fields produce an error if None.
#[proc_macro_derive( ToString )]
pub fn derive_to_string (input : TokenStream ) -> TokenStream {
let input = parse_macro_input! (input as DeriveInput );
let match_arms = match & input . data {
data . variants . iter () . map ( | v | {
# name :: #variant => stringify! (#variant) . to_string ()
return Error :: new_spanned ( & input . ident, "ToString only supports enums" )
pub fn to_variant_string ( & self ) -> String {
TokenStream :: from (expanded)
For AST traversal, macros can generate visitor trait implementations:
macro_rules! define_visitor {
$ ( fn visit_#name : ident ( &mut self , #param : ident : #ty : ty);) *
fn visit_# name ( &mut self , #param : #ty) {
fn visit_number ( &mut self , value : i64 );
fn visit_string ( &mut self , value : & str );
fn visit_boolean ( &mut self , value : bool );
Proc macros run during compilation. Each proc macro crate is compiled once and cached, but the Execution of the macro itself adds to compile time. Strategies to mitigate this:
Minimize syn features : Only enable the syn features you need. full pulls in every parser in the crate. If you only need structs, enable ["derive", "parsing"] instead of ["full"].
Avoid heavy computation : Proc macros should be fast. Do not perform network requests, file I/O, or expensive computations inside a proc macro.
Avoid generating excessive code : A macro that generates thousands of lines of code per invocation will slow down the compiler. Consider using generics or runtime dispatch instead.
Macros generate code at compile time, which increases binary size through monomorphization. A derive Macro that generates specialized code for every type it is applied to can cause code bloat. This is The same tradeoff as generic functions.
Proc macro crates are not incrementally compiled. Any change to a proc macro crate forces a full Recompilation of all crates that use it. Keep proc macro crates small and stable.
The paste crate enables identifier pasting in proc macros:
macro_rules! make_getter {
( $ name : ident, $ field : ident, $ ty : ty) => {
fn [<get_ $ name>]( & self ) -> &$ ty {
make_getter! (age, age, u32 );
make_getter! (name, name, String );
paste! transforms [<...>] blocks by concatenating identifiers. This is essential for generating Names that include parts of the input.
Using proc_macro::TokenStream directly instead of proc_macro2::TokenStream. The proc_macro::TokenStream type cannot be cloned or compared. Always convert to proc_macro2::TokenStream immediately at the proc-macro entry point and work with proc_macro2 throughout.
Forgetting #[macro_use] or proper imports. In edition 2018+, macro_rules! macros from external crates must be imported with use crate_name::macro_name; (without the !). The old #[macro_use] extern crate syntax is deprecated in edition 2021.
Hygiene violations in proc macros. Procedural macros are not hygienic for local variables. If your proc macro generates a variable named xIt can shadow a variable x at the call site. Use unique names (prefixed with __ or using paste!) to avoid collisions.
Pattern matching order in macro_rules!. Arms are matched top-to-bottom. A more specific pattern placed after a more general one will never match. Always put the most specific arms first and the most general (catch-all) arms last.
Using expr fragment specifier where tt is needed. The expr fragment specifier requires the matched expression to form a complete expression, which means it consumes trailing tokens like >> (right-shift) ambiguously. When in doubt, use tt and pass the tokens through to another macro or built-in.
Not handling all struct field types. When writing derive macros, you must handle both named fields (struct S { x: i32 }) and unnamed fields (struct S(i32)). Forgetting tuple structs or unit structs will cause a panic at derive time. Always match on all Fields variants.
Error messages that point to the macro invocation instead of the cause. Use quote_spanned! to attach the correct span to generated code, and use Error::new_spanned() to attach errors to specific tokens. This makes errors much easier to debug.
Proc macro crates cannot export regular items. A crate with proc-macro = true can only export proc-macro functions (#[proc_macro]``#[proc_macro_derive]``#[proc_macro_attribute]). Any non-macro exports will cause a compile error. Put shared types and helper functions in a separate crate and depend on it from both the proc-macro crate and the consumer.
macro_rules! arms with overlapping patterns. Two arms that can match the same input cause ambiguity. The compiler will error with “ambiguous macro call” if two arms both match and produce different expansions. If they produce the same expansion, the compiler silently picks the first one — but this is fragile and confusing.
Over-reliance on macros for simple abstractions. Not everything needs to be a macro. If a function, trait, or generic can solve the problem, use it instead. Macros are harder to read, harder to debug, and harder to document than ordinary Rust code. Reserve macros for cases where functions and traits genuinely cannot express the abstraction.
Macro Purpose println!Print to stdout with formatting eprintln!Print to stderr with formatting format!Create formatted string vec!Create a Vec<T> panic!Panic with a message assert!Assert a condition at runtime assert_eq!Assert two values are equal assert_ne!Assert two values are not equal dbg!Print and return a value (debug-only) todo!Mark unimplemented code (panics) unimplemented!Mark unimplemented code (panics) unreachable!Mark unreachable code (panics) compile_error!Emit a compile-time error concat!Concatenate string literals at compile time stringify!Convert tokens to a string literal include!Include a file as source code include_str!Include a file as a &'static str include_bytes!Include a file as &'static [u8] env!Read an environment variable at compile time option_env!Read an environment variable at compile time (returns Option) cfg!Check a configuration flag at compile time file!Current file path line!Current line number column!Current column number module_path!Current module path thread_local!Declare a thread-local static matches!Match an expression against a pattern
Crate Purpose synFull Rust parser for proc macros quoteCode generation via quasi-quoting proc-macro2Stable wrapper around proc_macro types thiserrorDerive macro for error types serdeDerive macros for serialization (Serialize``Deserialize) derive_builderBuilder pattern derive macro derive_moreAdditional derives (From, Into, Constructor, etc.) pasteIdentifier pasting in macros proc-macro-errorBetter error handling in proc macros darlingAttribute parsing helpers for proc macros macro_rules_attributeApply macro_rules! as attributes cargo-expandTool to view macro expansions trybuildTest harness for proc macro compile-fail tests instaSnapshot testing (useful for proc macro output)
This topic covers the core concepts of macros, including underlying theory, practical implementation, and key applications.
Key concepts include:
CPU architecture and the fetch-decode-execute cycle memory hierarchy (cache, RAM, virtual) input/output systems operating systems and scheduling interrupts and polling Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Rust Ownership and Borrowing — Ownership rules are enforced at compile time, while macros operate on the syntax tree before type checking.Rust Traits — Derive macros automatically generate trait implementations, reducing boilerplate.Rust Generics — Generic functions and trait bounds work alongside macros for type-safe code generation.Rust Error Handling — Procedural macros can generate error handling boilerplate for custom error types.