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)
flowchart TD
A[Rust Macros] --> B[Declarative: macro_rules!]
A --> C[Procedural: derive/attribute/function-like]
B --> D[Pattern Matching on Token Trees]
B --> E[Repetition: $()*]
C --> F[Derive Macros: #[derive Clone]]
C --> G[Attribute Macros: #[route GET /]]
C --> H[Function-like: sql!()]
D --> I[Compile-time Code Generation]
F --> I 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.