Loading page…
Rust walkthroughs
Loading page…
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 to TokenStream#variable to inject values#(...)* to iterate over collectionsToTokens trait — how types convert to tokensQuote is essential for derive macros, attribute macros, and function-like procedural macros.
# 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 }
}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);
}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);
}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);
}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);
}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);
}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);
}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);
}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);
}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);
}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);
}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);
}quote! macro to generate Rust code programmatically#variablequote::format_ident! to create formatted identifiers#(...)* for zero or more, #(...),* for comma-separated#(#a, #b)*Ident::new(name, Span::call_site())Literal::i32_unsuffixed(), Literal::string(), etc.proc_macro2 for token stream manipulation outside proc macros.into() to proc_macro::TokenStreamquote with syn for parsing input codeToTokens trait to convert custom types to tokens# by using ##