How do I generate Rust code programmatically?
Walkthrough
The quote crate provides a way to generate Rust code using quasi-quoting. It's the foundation of Rust procedural macros, allowing you to write Rust code that generates Rust code. The quote! macro lets you write template code with placeholders that get replaced with actual values. Combined with syn for parsing and proc_macro2 for token handling, quote forms the core of procedural macro development.
Key concepts:
quote!macro — write code templates that expand toTokenStream- Interpolation — use
#variableto inject values - Repetition — use
#(...)*to iterate over collections - TokenStream — the output type representing generated code
ToTokenstrait — how types convert to tokens
Quote is essential for derive macros, attribute macros, and function-like procedural macros.
Code Example
# Cargo.toml
[dependencies]
quote = "1.0"
proc-macro2 = "1.0"use quote::quote;
use proc_macro2::TokenStream;
fn main() {
let name = "my_function";
let tokens: TokenStream = quote! {
fn #name() -> i32 {
42
}
};
println!("{}", tokens);
// Output: fn my_function () -> i32 { 42 }
}Basic Code Generation
use quote::quote;
use proc_macro2::TokenStream;
fn main() {
// Simple function generation
let tokens = quote! {
fn hello() {
println!("Hello, World!");
}
};
println!("Function:\n{}\n", tokens);
// Struct generation
let name = "Point";
let tokens = quote! {
struct #name {
x: f64,
y: f64,
}
};
println!("Struct:\n{}\n", tokens);
// Enum generation
let enum_name = "Color";
let tokens = quote! {
enum #enum_name {
Red,
Green,
Blue,
}
};
println!("Enum:\n{}\n", tokens);
// Impl block
let type_name = "Point";
let tokens = quote! {
impl #type_name {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
};
println!("Impl:\n{}\n", tokens);
}Variable Interpolation
use quote::quote;
use proc_macro2::{TokenStream, Ident, Literal};
fn main() {
// Interpolate strings as identifiers
let fn_name = "calculate"; // &str
let tokens = quote! {
fn #fn_name() -> i32 {
100
}
};
println!("String as ident:\n{}\n", tokens);
// Interpolate with proc_macro2::Ident
let ident = quote::format_ident!("my_function_{}", 1);
let tokens = quote! {
fn #ident() -> i32 {
42
}
};
println!("Format ident:\n{}\n", tokens);
// Interpolate values
let value = 42i32;
let tokens = quote! {
const ANSWER: i32 = #value;
};
println!("Literal value:\n{}\n", tokens);
// Interpolate expressions
let x = 10;
let y = 20;
let tokens = quote! {
let sum = #x + #y;
};
println!("Expression:\n{}\n", tokens);
// Interpolate types
let type_name = "String";
let tokens = quote! {
fn process(input: #type_name) -> #type_name {
input
}
};
println!("Type interpolation:\n{}\n", tokens);
}Repetition with Iterators
use quote::quote;
use proc_macro2::TokenStream;
fn main() {
// Simple repetition
let fields = vec!["x", "y", "z"];
let tokens = quote! {
struct Vector {
#(
#fields: f64,
)*
}
};
println!("Simple repetition:\n{}\n", tokens);
// Multiple iterators
let names = vec!["id", "name", "active"];
let types = vec!["u32", "String", "bool"];
let tokens = quote! {
struct User {
#(
#names: #types,
)*
}
};
println!("Multiple iterators:\n{}\n", tokens);
// Repetition with separator
let items = vec![1, 2, 3];
let tokens = quote! {
let array = [#(#items),*];
};
println!("Array with separator:\n{}\n", tokens);
// Repetition with block
let fields = vec!["id", "name"];
let tokens = quote! {
fn print_fields(&self) {
#(
println!("{}: {:?}", stringify!(#fields), self.#fields);
)*
}
};
println!("Block repetition:\n{}\n", tokens);
}Generating Structs and Enums
use quote::quote;
use proc_macro2::{TokenStream, Ident};
fn generate_struct(name: &str, fields: &[(&str, &str)]) -> TokenStream {
let field_names: Vec<&str> = fields.iter().map(|(name, _)| *name).collect();
let field_types: Vec<&str> = fields.iter().map(|(_, ty)| *ty).collect();
quote! {
struct #name {
#(
#field_names: #field_types,
)*
}
}
}
fn generate_enum(name: &str, variants: &[&str]) -> TokenStream {
quote! {
enum #name {
#(
#variants,
)*
}
}
}
fn generate_impl(struct_name: &str, methods: &[TokenStream]) -> TokenStream {
quote! {
impl #struct_name {
#(#methods)*
}
}
}
fn main() {
// Generate a struct
let struct_tokens = generate_struct("Person", &[
("id", "u32"),
("name", "String"),
("email", "String"),
]);
println!("Generated struct:\n{}\n", struct_tokens);
// Generate an enum
let enum_tokens = generate_enum("Status", &[
"Pending",
"Active",
"Completed",
"Failed",
]);
println!("Generated enum:\n{}\n", enum_tokens);
// Generate impl with methods
let getter1 = quote! {
pub fn id(&self) -> u32 {
self.id
}
};
let getter2 = quote! {
pub fn name(&self) -> &str {
&self.name
}
};
let impl_tokens = generate_impl("Person", &[getter1, getter2]);
println!("Generated impl:\n{}\n", impl_tokens);
}Generating Trait Implementations
use quote::quote;
use proc_macro2::TokenStream;
fn generate_debug_impl(
type_name: &str,
fields: &[&str],
) -> TokenStream {
quote! {
impl std::fmt::Debug for #type_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!(#type_name))
#(
.field(stringify!(#fields), &self.#fields)
)*
.finish()
}
}
}
}
fn generate_clone_impl(
type_name: &str,
fields: &[&str],
) -> TokenStream {
quote! {
impl Clone for #type_name {
fn clone(&self) -> Self {
Self {
#(
#fields: self.#fields.clone(),
)*
}
}
}
}
}
fn generate_default_impl(
type_name: &str,
fields: &[(&str, &str)],
) -> TokenStream {
let field_names: Vec<&str> = fields.iter().map(|(n, _)| *n).collect();
let defaults: Vec<TokenStream> = fields.iter()
.map(|(_, ty)| {
match *ty {
"i32" | "u32" | "i64" | "u64" => quote! { 0 },
"f64" => quote! { 0.0 },
"bool" => quote! { false },
"String" => quote! { String::new() },
_ => quote! { Default::default() },
}
})
.collect();
quote! {
impl Default for #type_name {
fn default() -> Self {
Self {
#(
#field_names: #defaults,
)*
}
}
}
}
}
fn main() {
let type_name = "User";
let fields = vec!["id", "name", "active"];
// Generate Debug implementation
let debug_impl = generate_debug_impl(type_name, &fields);
println!("Debug impl:\n{}\n", debug_impl);
// Generate Clone implementation
let clone_impl = generate_clone_impl(type_name, &fields);
println!("Clone impl:\n{}\n", clone_impl);
// Generate Default implementation
let typed_fields = vec![("id", "u32"), ("name", "String"), ("active", "bool")];
let default_impl = generate_default_impl(type_name, &typed_fields);
println!("Default impl:\n{}\n", default_impl);
}Generating Builder Pattern
use quote::quote;
use proc_macro2::TokenStream;
fn generate_builder(
struct_name: &str,
builder_name: &str,
fields: &[(&str, &str)],
) -> TokenStream {
let field_names: Vec<&str> = fields.iter().map(|(n, _)| *n).collect();
let field_types: Vec<&str> = fields.iter().map(|(_, t)| *t).collect();
// Generate option types for builder fields
let option_types: Vec<TokenStream> = field_types.iter()
.map(|t| quote! { Option<#t> })
.collect();
// Generate builder struct
let builder_struct = quote! {
pub struct #builder_name {
#(
#field_names: #option_types,
)*
}
};
// Generate setter methods
let setters: Vec<TokenStream> = fields.iter()
.map(|(name, ty)| {
quote! {
pub fn #name(mut self, value: #ty) -> Self {
self.#name = Some(value);
self
}
}
})
.collect();
// Generate build method
let build_method = quote! {
pub fn build(self) -> Result<#struct_name, &'static str> {
Ok(#struct_name {
#(
#field_names: self.#field_names.ok_or("Missing field")?,
)*
})
}
};
// Combine all parts
quote! {
#builder_struct
impl #builder_name {
pub fn new() -> Self {
Self {
#(
#field_names: None,
)*
}
}
#(#setters)*
#build_method
}
impl #struct_name {
pub fn builder() -> #builder_name {
#builder_name::new()
}
}
}
}
fn main() {
let tokens = generate_builder(
"HttpRequest",
"HttpRequestBuilder",
&[
("method", "String"),
("url", "String"),
("headers", "Vec<(String, String)>"),
("body", "Option<Vec<u8>>"),
],
);
println!("Generated builder:\n{}", tokens);
}Conditional Code Generation
use quote::quote;
use proc_macro2::TokenStream;
fn generate_serialization(
type_name: &str,
fields: &[(&str, &str, bool)], // (name, type, serialize)
) -> TokenStream {
let serializable_fields: Vec<&str> = fields.iter()
.filter(|(_, _, ser)| *ser)
.map(|(name, _, _)| *name)
.collect();
quote! {
impl #type_name {
fn to_map(&self) -> std::collections::HashMap<&'static str, String> {
let mut map = std::collections::HashMap::new();
#(
map.insert(stringify!(#serializable_fields), format!("{:?}", self.#serializable_fields));
)*
map
}
}
}
}
fn generate_method_with_feature(
method_name: &str,
feature_flag: &str,
body: TokenStream,
) -> TokenStream {
quote! {
#[cfg(feature = #feature_flag)]
pub fn #method_name() {
#body
}
}
}
fn generate_platform_specific(name: &str) -> TokenStream {
let unix_fn = quote::format_ident!("{}_unix", name);
let windows_fn = quote::format_ident!("{}_windows", name);
quote! {
#[cfg(unix)]
pub fn #name() {
#unix_fn()
}
#[cfg(windows)]
pub fn #name() {
#windows_fn()
}
#[cfg(unix)]
fn #unix_fn() {
println!("Unix implementation");
}
#[cfg(windows)]
fn #windows_fn() {
println!("Windows implementation");
}
}
}
fn main() {
// Conditional serialization
let ser_impl = generate_serialization("User", &[
("id", "u32", true),
("name", "String", true),
("password", "String", false), // Don't serialize
("email", "String", true),
]);
println!("Serialization:\n{}\n", ser_impl);
// Feature-gated method
let feature_method = generate_method_with_feature(
"async_operation",
"async",
quote! { println!("Async operation!"); }
);
println!("Feature method:\n{}\n", feature_method);
// Platform-specific
let platform = generate_platform_specific("get_config_dir");
println!("Platform specific:\n{}\n", platform);
}Working with Ident and Literal
use quote::quote;
use proc_macro2::{TokenStream, Ident, Literal, Span};
fn main() {
// Create identifiers programmatically
let ident1 = Ident::new("my_variable", Span::call_site());
let ident2 = Ident::new("MyStruct", Span::call_site());
let tokens = quote! {
struct #ident2 {
#ident1: i32,
}
};
println!("Created idents:\n{}\n", tokens);
// Format identifiers
let base_name = "field";
let idents: Vec<Ident> = (0..3)
.map(|i| quote::format_ident!("{}_{}", base_name, i))
.collect();
let tokens = quote! {
struct Data {
#(#idents: i32,)*
}
};
println!("Formatted idents:\n{}\n", tokens);
// Create literals
let int_lit = Literal::i32_unsuffixed(42);
let float_lit = Literal::f64_unsuffixed(3.14);
let str_lit = Literal::string("hello");
let tokens = quote! {
const INT: i32 = #int_lit;
const FLOAT: f64 = #float_lit;
const STR: &str = #str_lit;
};
println!("Literals:\n{}\n", tokens);
// Suffixed literals
let suffixed_u32 = Literal::u32_suffixed(100);
let suffixed_i64 = Literal::i64_suffixed(-1000);
let tokens = quote! {
let a = #suffixed_u32;
let b = #suffixed_i64;
};
println!("Suffixed literals:\n{}\n", tokens);
}Generating Match Expressions
use quote::quote;
use proc_macro2::TokenStream;
fn generate_match_for_enum(
enum_name: &str,
variants: &[(&str, Option<&[&str]>)], // (name, fields)
) -> TokenStream {
// Generate match arms
let arms: Vec<TokenStream> = variants.iter()
.map(|(name, fields)| {
match fields {
Some(field_names) => {
quote! {
#enum_name::#name { #(#field_names),* } => {
println!("{}: {:?}", stringify!(#name), (#(#field_names),*));
}
}
}
None => {
quote! {
#enum_name::#name => {
println!("{}", stringify!(#name));
}
}
}
}
})
.collect();
quote! {
fn describe(value: #enum_name) {
match value {
#(#arms)*
}
}
}
}
fn generate_string_to_enum(
enum_name: &str,
variants: &[&str],
) -> TokenStream {
let arms: Vec<TokenStream> = variants.iter()
.map(|v| {
let lower = v.to_lowercase();
quote! {
#lower => #enum_name::#v
}
})
.collect();
quote! {
impl std::str::FromStr for #enum_name {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
#(#arms,)*
_ => Err("Invalid variant"),
}
}
}
}
}
fn main() {
// Generate match for enum with variants
let describe_fn = generate_match_for_enum("Message", &[
("Quit", None),
("Move", Some(&["x", "y"])),
("Write", Some(&["text"])),
("ChangeColor", Some(&["r", "g", "b"])),
]);
println!("Match function:\n{}\n", describe_fn);
// Generate string parsing
let from_str = generate_string_to_enum("Color", &["Red", "Green", "Blue"]);
println!("FromStr impl:\n{}\n", from_str);
}Complete Example: Derive Macro Helper
use quote::quote;
use proc_macro2::{TokenStream, Ident, Span};
// Simulate generating a complete derive macro implementation
fn generate_getters_impl(
struct_name: &str,
fields: &[(&str, &str, bool)], // (name, type, is_public)
) -> TokenStream {
let struct_ident = Ident::new(struct_name, Span::call_site());
let getters: Vec<TokenStream> = fields.iter()
.filter(|(_, _, public)| *public)
.map(|(name, ty, _)| {
quote! {
pub fn #name(&self) -> &#ty {
&self.#name
}
}
})
.collect();
let setters: Vec<TokenStream> = fields.iter()
.filter(|(_, _, public)| *public)
.map(|(name, ty, _)| {
let setter_name = quote::format_ident!("set_{}", name);
quote! {
pub fn #setter_name(&mut self, value: #ty) {
self.#name = value;
}
}
})
.collect();
quote! {
impl #struct_ident {
#(#getters)*
#(#setters)*
}
}
}
fn generate_builder_impl(
struct_name: &str,
builder_name: &str,
fields: &[(&str, &str)],
) -> TokenStream {
let field_idents: Vec<Ident> = fields.iter()
.map(|(name, _)| Ident::new(name, Span::call_site()))
.collect();
let field_types: Vec<&str> = fields.iter().map(|(_, ty)| *ty).collect();
quote! {
pub struct #builder_name<T> {
phantom: std::marker::PhantomData<T>,
#(
#field_idents: #field_types,
)*
}
impl #builder_name<()> {
pub fn new() -> Self {
Self {
phantom: std::marker::PhantomData,
#(
#field_idents: Default::default(),
)*
}
}
}
}
}
fn generate_display_impl(
struct_name: &str,
fields: &[&str],
) -> TokenStream {
let field_count = fields.len();
quote! {
impl std::fmt::Display for #struct_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {{ ", stringify!(#struct_name))?;
#(
write!(f, "{}: {:?}", stringify!(#fields), self.#fields)?;
if 0 < #field_count - 1 {
write!(f, ", ")?;
}
)*
write!(f, " }}")
}
}
}
}
fn main() {
// Generate complete implementations for a struct
let struct_name = "User";
let fields = [
("id", "u32", true),
("name", "String", true),
("email", "String", true),
("internal_id", "u64", false), // Private
];
println!("// Generated for {} struct\n", struct_name);
let getters = generate_getters_impl(
struct_name,
&fields,
);
println!("// Getters and Setters\n{}\n", getters);
let public_fields: Vec<(&str, &str)> = fields.iter()
.filter(|(_, _, public)| *public)
.map(|(name, ty, _)| (*name, *ty))
.collect();
let builder = generate_builder_impl(
struct_name,
"UserBuilder",
&public_fields,
);
println!("// Builder\n{}\n", builder);
let all_field_names: Vec<&str> = fields.iter().map(|(n, _, _)| *n).collect();
let display = generate_display_impl(struct_name, &all_field_names);
println!("// Display\n{}\n", display);
}Quasi-Quoting Tips
use quote::quote;
use proc_macro2::TokenStream;
fn main() {
// Escaping: use # to write # literally
let tokens = quote! {
let raw_string = r"This is a raw string with # inside";
};
println!("Escaped #:\n{}\n", tokens);
// Nested quotes
let inner = quote! { println!("inner"); };
let outer = quote! {
fn outer() {
#inner
}
};
println!("Nested quotes:\n{}\n", outer);
// Dynamic field access
let field = "name";
let tokens = quote! {
let value = obj.#field;
};
println!("Dynamic field:\n{}\n", tokens);
// Method call
let method = "calculate";
let tokens = quote! {
let result = obj.#method();
};
println!("Method call:\n{}\n", tokens);
// Type parameters
let generic_param = "T";
let tokens = quote! {
fn process<#generic_param>(value: #generic_param) -> #generic_param {
value
}
};
println!("Generic:\n{}\n", tokens);
// Multiple type parameters
let params = vec!["K", "V"];
let tokens = quote! {
struct Map<#(#params),*> {
// ...
}
};
println!("Multiple generics:\n{}\n", tokens);
// Where clauses
let param = "T";
let bounds = vec!["Clone", "Debug", "Send"];
let tokens = quote! {
fn process<#param>(value: #param)
where
#param: #(#bounds)+*
{
// ...
}
};
println!("Where clause:\n{}\n", tokens);
}Summary
- Use
quote!macro to generate Rust code programmatically - Interpolate values with
#variable - Use
quote::format_ident!to create formatted identifiers - Repeat with
#(...)*for zero or more,#(...),*for comma-separated - Use multiple iterators with
#(#a, #b)* - Create idents with
Ident::new(name, Span::call_site()) - Create literals with
Literal::i32_unsuffixed(),Literal::string(), etc. - Use
proc_macro2for token stream manipulation outside proc macros - For proc macros, convert with
.into()toproc_macro::TokenStream - Combine
quotewithsynfor parsing input code - Use
ToTokenstrait to convert custom types to tokens - Generate struct/enum definitions, impl blocks, traits, match expressions
- Escape
#by using## - Quote is the foundation of derive macros, attribute macros, and function-like macros
