Generating code with quote
Building token streams from strings is fragile and unreadable. The
quote crate provides a macro that lets you write code generation
that looks almost like regular Rust.
The quote! macro
quote! takes Rust-like syntax and produces a proc_macro2::TokenStream:
#![allow(unused)]
fn main() {
use quote::quote;
let tokens = quote! {
fn hello() -> &'static str {
"hello!"
}
};
}
Interpolation with #
The real power of quote! is interpolation. Use #variable to splice a value into the
generated code:
#![allow(unused)]
fn main() {
use quote::quote;
use syn::Ident;
let name = Ident::new("Foo", proc_macro2::Span::call_site());
let tokens = quote! {
#[automatically_derived]
impl #name {
pub fn type_name() -> &'static str {
stringify!(#name)
}
}
};
}
stringify! is a built-in macro that
turns its argument into a string literal at compile time — stringify!(Foo) becomes "Foo".
It’s useful in generated code because it works with any identifier without needing to build
string literals yourself.
This generates:
#![allow(unused)]
fn main() {
#[automatically_derived]
impl Foo {
pub fn type_name() -> &'static str {
stringify!(Foo) // expands to "Foo"
}
}
}
#[automatically_derived]
Every impl block your derive macro emits should carry #[automatically_derived]. It tells
the Rust toolchain that the implementation was generated, not hand-written, so Clippy and
other tools suppress lints inside it — the user can’t edit generated code, so warning them
about it would be noise. The standard library’s own derive macros (Clone, Debug,
PartialEq, …) all emit it: make it a reflex.
Any type that implements the ToTokens
trait can be interpolated. syn types like Ident, Type, and Path all implement it, so
you can splice parsed values directly into generated code.
Repetition with #( ... )*
When you need to generate code for each item in a collection, quote! supports repetition
syntax similar to macro_rules!:
#![allow(unused)]
fn main() {
use quote::quote;
let field_names = vec!["x", "y", "z"];
let field_idents: Vec<syn::Ident> = field_names
.iter()
.map(|n| syn::Ident::new(n, proc_macro2::Span::call_site()))
.collect();
let tokens = quote! {
fn field_names() -> &'static [&'static str] {
&[#(stringify!(#field_idents)),*]
}
};
}
Let’s break down #(stringify!(#field_idents)),*:
#( ... ),*— the repetition operator. It iterates overfield_idents, producing each element separated by commas (,). The*means “zero or more times.”stringify!(#field_idents)— the body repeated for each element.#field_identsis replaced with the currentIdenton each iteration.
So for field_idents = [x, y, z], this expands to:
#![allow(unused)]
fn main() {
stringify!(x), stringify!(y), stringify!(z)
}
which at compile time becomes "x", "y", "z".
This is the same pattern you’ll use to generate code for each field in a struct.
quote! vs string formatting
Compare these two approaches:
#![allow(unused)]
fn main() {
// String formatting — fragile, no syntax highlighting, easy to get wrong
let code = format!(
"impl {} {{ fn count() -> usize {{ {} }} }}",
name, count
);
let tokens: TokenStream = code.parse().unwrap();
// quote! — looks like Rust, catches many errors at compile time
let tokens = quote! {
impl #name {
fn count() -> usize { #count }
}
};
}
quote! is not just more readable — it catches many mistakes at compile time rather than
producing confusing errors for macro users.
format_ident!
The format_ident! macro lets you
construct new identifiers programmatically, similar to how format! builds strings:
#![allow(unused)]
fn main() {
use quote::format_ident;
let field = "name";
let getter = format_ident!("get_{field}");
// `getter` is the identifier `get_name`
}
This is useful when your macro needs to generate new method or type names based on the input.
quote_spanned!
quote! has a variant, quote_spanned!,
that lets you attach span information to the generated tokens. This controls where error
messages point when the generated code fails to compile. We won’t need it in this workshop —
we report errors with syn::Error instead (chapter 4) — but it’s a useful tool to know about
when you want a compile error in your generated code to point at a specific span in the
user’s source.
parse_quote! — the input counterpart
When writing unit tests for your macros, you need to construct syn types as inputs.
syn::parse_quote! is the idiomatic
way to do that — the mirror image of quote!: where quote! turns Rust syntax into a
TokenStream for output, parse_quote! turns it into a typed syn value for input. Despite
the name, it lives in syn, not quote.
#![allow(unused)]
fn main() {
use syn::{DeriveInput, parse_quote};
let input: DeriveInput = parse_quote! { struct Foo { x: i32 } };
}
This replaces syn::parse_str("...").unwrap(): you write real tokens instead of a string,
so typos are caught at compile time rather than at runtime.
Exercise
The exercise for this section is located in 03_basics/07_quote
The exercise provides a working derive macro that generates a field_names() method using
string formatting. Rewrite it to use quote! instead — the logic stays the same, only the
code generation changes.