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:

  1. quote! macro — write code templates that expand to TokenStream
  2. Interpolation — use #variable to inject values
  3. Repetition — use #(...)* to iterate over collections
  4. TokenStream — the output type representing generated code
  5. ToTokens trait — 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_macro2 for token stream manipulation outside proc macros
  • For proc macros, convert with .into() to proc_macro::TokenStream
  • Combine quote with syn for parsing input code
  • Use ToTokens trait 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