Procedural macros¶
Level: 201 → 301 · working knowledge to deep dive
One line: A procedural macro is Rust code the compiler runs on your code — tokens in, tokens out — and this chapter goes from reading the ones you use, through syn, quote and darling, to writing derive, function-like and attribute macros whose errors point at the right token.
The six parts follow a one-day course outline, in its order. Every lesson's output, expansion and compiler error comes from a recorded run; where a course or tutorial written for syn 2 says something syn 3 or rustc 1.98.0 does not, the page says so.
1. Macros you already use¶
| Lesson | What it answers |
|---|---|
| Three kinds of procedural macro | What a derive, a function-like macro and an attribute each receive and return — and why #[derive(Debug)] is not one of them |
Expanding thiserror |
Every line #[derive(Error)] writes, read from the expansion |
A function, macro_rules!, or a procedural macro |
When a procedural macro is the right call, and what it costs when it is not |
2. The toolkit¶
| Lesson | What it answers |
|---|---|
| A proc-macro crate | Why the macros need a crate of their own, and what that crate may export |
| Tokens and token streams | The four kinds of token tree a macro reads and writes, with their spacing and spans |
proc-macro2 makes it testable |
Why proc_macro panics outside the compiler, and the wrapper that does not |
Parsing with syn |
Tokens into a syntax tree: DeriveInput, Data, Fields |
Generating with quote |
Rust with holes in it: interpolation, repetition, new identifiers |
| The re-export pattern | Why a trait and its derive ship as separate crates, and how the user sees one |
Testing with trybuild |
Putting the compiler errors your users see under test |
3. Derive macros¶
| Lesson | What it answers |
|---|---|
| Every struct and enum shape | Named, tuple and unit fields, and enums with all three |
| Absolute paths and hygiene | Generated code that compiles in a crate with its own Result, and what mixed_site does and does not protect |
Errors: from panic! to syn::Error |
Three ways to fail, and the one that underlines the right token |
| Helper attributes by hand | Container and field attributes, parsed with parse_nested_meta |
Helper attributes with darling |
The same attributes, declared as a struct |
Generics, lifetimes and where |
An impl that repeats every parameter and bound the type has, and which bound to add |
4. Function-like macros¶
| Lesson | What it answers |
|---|---|
Why println! is a macro |
What no function signature can take, and what format_args! checks while compiling |
| Parsing arbitrary tokens | Your own syntax between the delimiters, with syn::parse::Parse |
When macro_rules! runs out |
The exact point a declarative macro stops, and a procedural one starts to pay |
A routes! macro |
A small DSL, checked at compile time |
5. Attribute macros¶
| Lesson | What it answers |
|---|---|
| Parse, tweak, re-emit | Changing a function and handing all of it back |
| Re-emit the item on error | One error for the user instead of one per method call |
A #[retry] attribute |
#[retry(times = 3, delay_ms = 100)], with arguments parsed by darling |
6. Putting it all together¶
| Lesson | What it answers |
|---|---|
A StateMachine derive |
Variants, helper attributes, transitions rejected at compile time, identifiers that never clash |
How these pages are checked¶
A procedural macro cannot be a single file: it needs a crate of its own, and usually syn and quote from crates.io. So a lesson here keeps a small Cargo workspace in its demo/ folder, with a committed Cargo.lock, and declares the commands it runs in demo/cargo_runs.toml. tools/run_cargo_demos.py runs each one with --locked, holds its output to an answer key in demo/keys/, and pastes that output into the page. Compiler errors are recorded the same way, from runs that are declared to fail. CI runs it as its own job, beside the one that checks every single-file example.
To run a demo yourself, cd into its demo/ folder and use the command the page shows. The toolchain is the repo's pin, 1.98.0.
What this chapter does not cover¶
macro_rules!itself. That is Declarative macros, after Macros introduces the!. This chapter compares declarative macros with procedural ones, and teaches only the second.- Build scripts. A
build.rsalso generates code before your crate compiles, but it writes files rather than transforming tokens, and it sees none of your source.
See also¶
- Macros — what the
!means at a call site - Declarative macros —
macro_rules!, the other half of the topic - Traits — nearly every derive generates a trait
impl - Generics — what a derive on a generic type has to repeat
- The Rust Reference: Procedural macros ↗
- The
proc_macrocrate ↗
Po polsku¶
Makro proceduralne (procedural macro) to kod w Ruscie, który kompilator uruchamia na twoim kodzie: dostaje tokeny (tokens) i zwraca tokeny. Rozdział idzie w kolejności jednodniowego kursu: najpierw makra, których już używasz (#[derive(Error)] z thiserror rozwinięte linijka po linijce), potem narzędzia — osobny crate typu proc-macro, proc-macro2, syn do parsowania i quote do generowania kodu, trybuild do testowania komunikatów błędów — a dalej trzy rodzaje makr po kolei: derive, funkcyjne i atrybutowe. Na końcu jest projekt łączący wszystko: #[derive(StateMachine)], który odrzuca niedozwolone przejście między stanami już podczas kompilacji, z błędem wskazującym dokładnie ten token, który jest zły.
Każda strona opiera się na małym workspace Cargo w folderze demo/; to, co program wypisał — także komunikaty kompilatora — wkleja narzędzie, nie człowiek.
Szukaj po polsku: makra proceduralne w Ruscie · syn i quote · rust derive macro tutorial · rust proc macro workshop · darling FromDeriveInput