What is the purpose of quote::format_ident! for generating dynamic identifiers in procedural macros?

quote::format_ident! generates syn::Ident identifiers dynamically at macro expansion time by formatting a string pattern with runtime values, enabling procedural macros to create identifiers that don't exist in the source code being processed. Unlike quote! which only works with existing tokens, format_ident! constructs new identifiers from computed values, allowing macros to generate derived names like new_ prefixes, _builder suffixes, or composite names from field names—all while maintaining proper Rust identifier syntax and span information for accurate error reporting.

Basic Identifier Generation

use quote::format_ident;
 
fn main() {
    // Create a simple identifier
    let ident = format_ident!("MyStruct");
    println!("Identifier: {}", ident);  // MyStruct
    
    // With underscore prefix
    let private = format_ident!("_private_field");
    println!("Private: {}", private);  // _private_field
}

format_ident! creates identifiers from string literals, similar to format! for strings.

Format String with Values

use quote::format_ident;
 
fn main() {
    let prefix = "new";
    let name = "Builder";
    
    // Format identifier like format! macro
    let method = format_ident!("{}_{}", prefix, name);
    println!("Method: {}", method);  // new_Builder
    
    // With positional arguments
    let getter = format_ident!("get_{}", "value");
    println!("Getter: {}", getter);  // get_value
    
    // With named arguments
    let setter = format_ident!("set_{field}", field = "name");
    println!("Setter: {}", setter);  // set_name
}

Like format!, format_ident! supports positional and named arguments.

Numeric Suffix Generation

use quote::format_ident;
 
fn main() {
    // Generate numbered identifiers
    for i in 0..5 {
        let var = format_ident!("field_{}", i);
        println!("Variable: {}", var);  // field_0, field_1, ...
    }
    
    // Useful for generating unique names
    let fields: Vec<_> = (0..3)
        .map(|i| format_ident!("tuple_{}", i))
        .collect();
    
    for field in fields {
        println!("Field: {}", field);
    }
}

Numeric suffixes are common for generating unique identifiers programmatically.

Span Information Preservation

use quote::format_ident;
use syn::parse_quote;
use proc_macro2::Span;
 
fn main() {
    // Default span (call site)
    let ident_default = format_ident!("generated");
    println!("Default span: {:?}", ident_default.span());
    
    // Specific span for error reporting
    let span = Span::call_site();
    let ident_spanned = format_ident!("generated_at_call_site");
    
    // Span from existing token
    let input: syn::Ident = parse_quote!(original);
    let derived = format_ident!("{}_derived", input);
    
    // Derived identifier inherits span from input
    // This makes error messages point to the source
    println!("Derived from input: {}", derived);
}

Spans control error message locations; derived identifiers should inherit source spans.

Procedural Macro Example: Derive Builder

use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{parse_macro_input, DeriveInput, Data, Fields};
 
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    let name = &input.ident;
    let builder_name = format_ident!("{}Builder", name);
    
    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => &fields.named,
            _ => panic!("Only named fields supported"),
        },
        _ => panic!("Only structs supported"),
    };
    
    let field_names: Vec<_> = fields.iter()
        .map(|f| f.ident.as_ref().unwrap())
        .collect();
    
    let builder_fields = fields.iter().map(|f| {
        let name = f.ident.as_ref().unwrap();
        let ty = &f.ty;
        quote! {
            #name: Option<#ty>
        }
    });
    
    let builder_inits = fields.iter().map(|f| {
        let name = f.ident.as_ref().unwrap();
        quote! {
            #name: None
        }
    });
    
    let setter_methods = fields.iter().map(|f| {
        let name = f.ident.as_ref().unwrap();
        let ty = &f.ty;
        // format_ident creates setter method names
        let setter_name = format_ident!("with_{}", name);
        quote! {
            pub fn #setter_name(mut self, #name: #ty) -> Self {
                self.#name = Some(#name);
                self
            }
        }
    });
    
    let build_fields = fields.iter().map(|f| {
        let name = f.ident.as_ref().unwrap();
        quote! {
            #name: self.#name.take().ok_or(concat!("Missing field: ", stringify!(#name)))?
        }
    });
    
    let expanded = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
        
        impl #name {
            pub fn builder() -> #builder_name {
                #builder_name {
                    #(#builder_inits),*
                }
            }
        }
        
        impl #builder_name {
            #(#setter_methods)*
            
            pub fn build(self) -> Result<#name, Box<dyn std::error::Error>> {
                Ok(#name {
                    #(#build_fields),*
                })
            }
        }
    };
    
    expanded.into()
}

format_ident! generates {}Builder type name and with_{} setter methods from field names.

Field-to-Method Mapping

use quote::{quote, format_ident};
use syn::{parse_quote, Ident};
 
fn generate_accessors(struct_name: &Ident, fields: &[&str]) -> proc_macro2::TokenStream {
    let getters = fields.iter().map(|field| {
        let field_ident = parse_quote!(#field);
        let getter = format_ident!("get_{}", field);
        quote! {
            pub fn #getter(&self) -> &str {
                &self.#field_ident
            }
        }
    });
    
    let setters = fields.iter().map(|field| {
        let field_ident = parse_quote!(#field);
        let setter = format_ident!("set_{}", field);
        quote! {
            pub fn #setter(&mut self, value: String) {
                self.#field_ident = value;
            }
        }
    });
    
    quote! {
        impl #struct_name {
            #(#getters)*
            #(#setters)*
        }
    }
}
 
fn main() {
    let fields = vec!["name", "email", "address"];
    let struct_name: Ident = parse_quote!(User);
    
    let tokens = generate_accessors(&struct_name, &fields);
    println!("{}", tokens);
}

Generate get_{field} and set_{field} methods from field names.

Keyword Escaping

use quote::format_ident;
 
fn main() {
    // Raw identifiers for Rust keywords
    let type_field = format_ident!("r#type");
    println!("Type field: {}", type_field);  // r#type
    
    let match_field = format_ident!("r#match");
    println!("Match field: {}", match_field);  // r#match
    
    // Use in generated code
    use quote::quote;
    let tokens = quote! {
        struct Response {
            #type_field: String,
            #match_field: i32,
        }
    };
    println!("Generated: {}", tokens);
}

r# prefix creates raw identifiers for Rust keywords as field names.

Case Conversion Patterns

use quote::format_ident;
use heck::{SnakeCase, CamelCase};
 
fn main() {
    // Convert case in generated identifiers
    let field_name = "user_name";
    
    // Snake case (already snake case)
    let db_column = format_ident!("{}", field_name);
    
    // CamelCase for method names
    let field_camel = field_name.to_camel_case();  // UserName
    let getter = format_ident!("get{}", field_camel);
    
    // Snake case for database fields
    let field_snake = "UserName".to_snake_case();  // user_name
    
    println!("DB column: {}", db_column);  // user_name
    println!("Getter: {}", getter);  // getUserName
}

Combine with case conversion crates for conventional naming patterns.

Tuple Struct Indexing

use quote::{quote, format_ident};
 
fn main() {
    // Generate tuple access patterns
    let indices: Vec<_> = (0..3)
        .map(|i| format_ident!("v{}", i))
        .collect();
    
    let tokens = quote! {
        struct Point(#(#indices),*);
        
        impl Point {
            fn new(#(#indices: f64),*) -> Self {
                Point(#(#indices),*)
            }
        }
    };
    
    println!("{}", tokens);
}

Generate v0, v1, v2 for tuple struct elements.

Module and Type Generation

use quote::{quote, format_ident};
use syn::Ident;
 
fn generate_module(types: &[&str]) -> proc_macro2::TokenStream {
    let modules = types.iter().map(|ty| {
        let mod_name = format_ident!("{}", ty.to_lowercase());
        let type_name = format_ident!("{}", ty);
        let new_fn = format_ident!("new_{}", ty.to_lowercase());
        
        quote! {
            pub mod #mod_name {
                use super::*;
                
                pub struct #type_name {
                    value: String,
                }
                
                pub fn #new_fn(value: String) -> #type_name {
                    #type_name { value }
                }
            }
        }
    });
    
    quote! {
        #(#modules)*
    }
}
 
fn main() {
    let types = vec!["User", "Post", "Comment"];
    let tokens = generate_module(&types);
    println!("{}", tokens);
}

Generate nested modules and types from a list of names.

Trait Implementation Generation

use quote::{quote, format_ident};
use syn::{parse_quote, Ident};
 
fn generate_from_impl(target: &Ident, variants: &[&str]) -> proc_macro2::TokenStream {
    let impls = variants.iter().map(|variant| {
        let variant_ident = format_ident!("{}", variant);
        let variant_lower = variant.to_lowercase();
        let conversion_fn = format_ident!("from_{}", variant_lower);
        
        quote! {
            impl From<#variant_ident> for #target {
                fn from(v: #variant_ident) -> Self {
                    #target::#variant_ident(v)
                }
            }
        }
    });
    
    quote! {
        #(#impls)*
    }
}
 
fn main() {
    let target: Ident = parse_quote!(Value);
    let variants = vec!["Int", "Float", "String", "Bool"];
    
    let tokens = generate_from_impl(&target, &variants);
    println!("{}", tokens);
}

Generate From implementations for enum variants.

Visibility-Aware Generation

use quote::{quote, format_ident};
use syn::Visibility;
 
fn generate_struct_with_visibility(
    name: &str,
    fields: &[(&str, &str)],
    vis: Visibility,
) -> proc_macro2::TokenStream {
    let struct_name = format_ident!("{}", name);
    let builder_name = format_ident!("{}Builder", name);
    
    let field_idents: Vec<_> = fields.iter()
        .map(|(name, _)| format_ident!("{}", name))
        .collect();
    
    let field_types: Vec<_> = fields.iter()
        .map(|(_, ty)| syn::parse_str::<syn::Type>(ty).unwrap())
        .collect();
    
    quote! {
        #vis struct #struct_name {
            #(
                #vis #field_idents: #field_types,
            )*
        }
        
        #vis struct #builder_name {
            #(
                #field_idents: Option<#field_types>,
            )*
        }
    }
}
 
fn main() {
    use syn::parse_quote;
    
    let fields = vec![
        ("id", "u64"),
        ("name", "String"),
        ("active", "bool"),
    ];
    
    let tokens = generate_struct_with_visibility("User", &fields, parse_quote!(pub));
    println!("{}", tokens);
}

Generated identifiers respect visibility modifiers from input.

Span Inheritance for Error Messages

use quote::format_ident;
use syn::{parse_quote, spanned::Spanned};
 
fn process_ident(input: syn::Ident) -> proc_macro2::TokenStream {
    // Use the input's span for the generated identifier
    let derived = format_ident!("{}_processed", &input);
    
    // This way errors point to the original identifier
    // rather than the macro call site
    
    // Alternative: explicit span parameter
    let derived_explicit = format_ident!("{}_derived", input.span());
    
    quote! {
        let #derived = #input.process();
    }
}
 
fn main() {
    let input: syn::Ident = parse_quote!(my_data);
    let tokens = process_ident(input);
    println!("{}", tokens);
}

Inheriting spans ensures error messages point to source locations.

Generated Test Functions

use quote::{quote, format_ident};
 
fn generate_test_functions(test_cases: &[(&str, i32, i32)]) -> proc_macro2::TokenStream {
    let tests = test_cases.iter().map(|(name, a, b)| {
        let test_fn = format_ident!("test_{}", name);
        let expected = a + b;
        
        quote! {
            #[test]
            fn #test_fn() {
                assert_eq!(#a + #b, #expected);
            }
        }
    });
    
    quote! {
        #(#tests)*
    }
}
 
fn main() {
    let test_cases = vec![
        ("add_simple", 1, 2),
        ("add_zero", 0, 5),
        ("add_negative", -3, 4),
    ];
    
    let tokens = generate_test_functions(&test_cases);
    println!("{}", tokens);
}

Generate test functions with derived names from test case descriptions.

Comparison with quote! Alone

use quote::{quote, format_ident};
use syn::parse_quote;
 
fn main() {
    // quote! cannot create new identifiers
    let existing: syn::Ident = parse_quote!(existing_name);
    
    // This works - existing identifier
    let tokens1 = quote! {
        let #existing = 42;
    };
    println!("With existing: {}", tokens1);
    
    // This DOESN'T work - quote! doesn't format strings into idents
    // let prefix = "new";
    // let tokens2 = quote! {
    //     let #prefix_name = 42;  // Won't work
    // };
    
    // format_ident! creates new identifiers
    let prefix = "new";
    let new_ident = format_ident!("{}_name", prefix);
    
    let tokens3 = quote! {
        let #new_ident = 42;
    };
    println!("With format_ident: {}", tokens3);
}

quote! interpolates existing tokens; format_ident! creates new identifiers.

Synthesis

When to use format_ident!:

Scenario Example
Derive type names {}Builder, {}Error
Generate setters with_{}, set_{}
Numeric suffixes field_0, tuple_1
Prefix/suffix new_{}, {}_ref
Dynamic module names From input strings

format_ident! vs quote!:

Aspect quote! format_ident!
Creates identifiers No, interpolates only Yes, from format strings
Input Existing tokens Format string + values
Use case Code generation Identifier generation
Span handling Preserves input spans Configurable spans

Key insight: quote::format_ident! fills the gap between static code templates in quote! and dynamically computed identifier names in procedural macros. While quote! interpolates existing tokens into templates, format_ident! constructs new syn::Ident values from format strings at macro expansion time, enabling macros to generate derived names like UserBuilder from User, with_name from name, or field_0 from field and 0. The generated identifiers carry span information for error reporting—by default using the call site span, but inheriting input spans when derived from existing tokens ensures error messages point to the source. Use format_ident! whenever macro output needs identifiers that don't exist in the input, combined with quote! to embed those identifiers in generated code structures.