On this page
Procedural Macros
Procedural macros run Rust code at compile time, receiving a TokenStream and outputting a TokenStream—derive macros automatically generate impls for structs/enums, attribute macros modify arbitrary items, and function-like macros are called like functions but process token streams. syn parses TokenStreams into ASTs, quote converts ASTs back into TokenStreams, and cargo expand lets you view the expansion results—the three form the standard workflow for writing procedural macros.
Three Types of Procedural Macros
// 1. derive: #[derive(MyTrait)] — automatically generates impls for types
// 2. attribute: #[my_attr] — modifies entire items (fn, struct, impl)
// 3. function-like: my_macro!(...) — similar to macro_rules! but uses a Rust function to process input
Procedural macros are Rust code that runs at compile time, receiving and producing TokenStreams—a sequence of tokens. This is more flexible than macro_rules!—you can perform arbitrarily complex compile-time computations.
syn + quote: The De Facto Standard
use TokenStream;
use ;
use quote;
syn parses TokenStreams into ASTs (similar to the Rust compiler's frontend). quote! converts Rust code back into a token stream, supporting interpolation (#name).
Limitations of proc-macro Crates
- Must be a separate crate (
Cargo.toml:[lib] proc-macro = true) - Can only export proc macro functions, not regular functions, types, or constants
- Dependencies cannot form cycles—the proc-macro crate can depend on other crates, but cannot be depended upon by crates in the same workspace
cargo expand: Expanding Macros
This is the fastest way to understand macro behavior—seeing the actual code the compiler processes.
References
- syn: docs.rs/syn, quote: docs.rs/quote
- cargo-expand: github.com/dtolnay/cargo-expand
Keywords: proc macro, derive, syn, quote, TokenStream, cargo expand, attribute macro, function-like macro