---
title: Procedural Macros
url: https://doc.liz6.com/en/rust/08-macro-system/02-procedural-macros
locale: en
area: rust
tags:
- rust
- macro-system
date: 2026-06-30
modified: 2026-07-16
description: 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.
---

# 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

```rust
// 1. derive: #[derive(MyTrait)] — automatically generates impls for types
#[proc_macro_derive(MyTrait)]
pub fn my_derive(input: TokenStream) -> TokenStream { ... }

// 2. attribute: #[my_attr] — modifies entire items (fn, struct, impl)
#[proc_macro_attribute]
pub fn my_attr(attr: TokenStream, item: TokenStream) -> TokenStream { ... }

// 3. function-like: my_macro!(...) — similar to macro_rules! but uses a Rust function to process input
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream { ... }
```

Procedural macros are Rust code that runs at compile time, receiving and producing `TokenStream`s—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

```rust
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use quote::quote;

#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);   // TokenStream → AST
    let name = input.ident;
    let gen = quote! {
        impl Hello for #name {
            fn hello() { println!("Hello, {}!", stringify!(#name)); }
        }
    };
    gen.into()                                               // back to TokenStream
}
```

`syn` parses `TokenStream`s 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

```bash
cargo install cargo-expand
cargo expand                          # expand all macros in the current crate
cargo expand my_function              # expand a specific function
```

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*
