What is the difference between quote::format_ident! and quote::Ident::new for generating identifiers in proc macros?

format_ident! provides formatted string interpolation for identifier creation with automatic span inference from interpolated values, while Ident::new requires an exact string and explicit span specification. The format_ident! macro is the ergonomic choice for building identifiers dynamically—combining static text with variable values—where Ident::new is the foundational constructor when you have a complete identifier string ready.

Creating Identifiers with Ident::new

use proc_macro2::Ident;
use quote::quote;
use syn::spanned::Spanned;
 
fn basic_ident_creation() {
    // Ident::new takes:
    // 1. The identifier string (must be a valid Rust identifier)
    // 2. The span (where the identifier "comes from" in source code)
    
    let ident = Ident::new("my_function", proc_macro2::Span::call_site());
    
    // Use in a quote
    let tokens = quote! {
        fn #ident() {}
    };
    
    // Generates: fn my_function() {}
}

Ident::new is the fundamental constructor for creating identifiers from strings.

Creating Identifiers with format_ident!

use quote::format_ident;
use proc_macro2::Ident;
 
fn formatted_ident_creation() {
    let prefix = "get";
    let suffix = "value";
    
    // format_ident! works like format!, but produces an Ident
    let ident = format_ident!("{}_{}", prefix, suffix);
    
    // Generates identifier: get_value
    
    // More complex formatting
    let index = 0;
    let field_ident = format_ident!("field_{}", index);
    // Generates identifier: field_0
}

format_ident! interpolates values into identifier strings with format!-like syntax.

Span Handling: The Key Difference

use proc_macro2::Span;
use quote::{format_ident, quote};
use proc_macro2::Ident;
 
fn span_handling(input: &syn::Ident) {
    // Ident::new requires explicit span
    let ident_new = Ident::new("generated", Span::call_site());
    
    // format_ident! can infer span from interpolated values
    // When you interpolate a token, its span is used
    let ident_formatted = format_ident!("generated_{}", input);
    // The span comes from `input`, not call_site()
    
    // This is important for error messages and hygiene
    // Errors will point to the source of the input, not the macro
}

format_ident! automatically inherits spans from interpolated identifiers, improving error messages.

Explicit Span with format_ident!

use proc_macro2::Span;
use quote::format_ident;
 
fn explicit_span() {
    // format_ident! also accepts span as the last argument
    let ident = format_ident!("my_ident", span = Span::call_site());
    
    // Or with formatting
    let name = "value";
    let ident = format_ident!("get_{}", name, span = Span::call_site());
    
    // This is equivalent to:
    let ident2 = proc_macro2::Ident::new(&format!("get_{}", name), Span::call_site());
}

You can override the span inference with an explicit span = argument.

Building Derived Identifiers

use quote::{format_ident, quote};
use proc_macro2::Ident;
 
fn derive_identifiers(base: &syn::Ident) -> proc_macro2::TokenStream {
    // Common pattern: create related identifiers from a base
    
    // With format_ident!
    let getter = format_ident!("get_{}", base);
    let setter = format_ident!("set_{}", base);
    let field = format_ident!("{}_field", base);
    
    quote! {
        fn #getter(&self) -> &str {
            &self.#field
        }
        
        fn #setter(&mut self, value: String) {
            self.#field = value;
        }
    }
    
    // With Ident::new (more verbose)
    let getter_str = format!("get_{}", base);
    let getter = Ident::new(&getter_str, base.span());
    
    let setter_str = format!("set_{}", base);
    let setter = Ident::new(&setter_str, base.span());
    
    let field_str = format!("{}_field", base);
    let field = Ident::new(&field_str, base.span());
}

format_ident! is much cleaner for deriving identifiers from input.

Interpolating Multiple Types

use quote::format_ident;
use proc_macro2::Ident;
 
fn interpolate_types(base: &syn::Ident, index: usize) {
    // format_ident! handles multiple types
    
    // Strings and string literals
    let ident1 = format_ident!("{}_suffix", base);
    
    // Numbers
    let ident2 = format_ident!("field_{}", index);
    
    // Multiple values
    let ident3 = format_ident!("{}_{}", base, index);
    
    // Other idents
    let other_ident = format_ident!("prefix");
    let ident4 = format_ident!("{}_{}", base, other_ident);
    
    // Complex expressions
    let ident5 = format_ident!("var_{}", index + 1);
}

format_ident! interpolates identifiers, strings, numbers, and expressions.

When Ident::new Is Appropriate

use proc_macro2::{Ident, Span};
use quote::quote;
 
fn ident_new_appropriate() {
    // When the identifier is completely determined
    let keywords = ["create", "read", "update", "delete"];
    
    let methods: Vec<Ident> = keywords
        .iter()
        .map(|&kw| Ident::new(kw, Span::call_site()))
        .collect();
    
    // When reading from external source
    let name = "external_name";  // Could come from config, file, etc.
    let ident = Ident::new(name, Span::call_site());
    
    // When you need precise span control
    let span = Span::call_site();
    let ident = Ident::new("precise", span);
    
    // When no formatting is needed
    let simple_ident = Ident::new("simple", Span::call_site());
}

Use Ident::new when you have the complete identifier string and explicit span.

Hygiene and Spans

use quote::{format_ident, quote};
use proc_macro2::Span;
 
fn hygiene_example(input_ident: &syn::Ident) {
    // Hygiene determines whether generated code can access
    // or shadow names in the caller's scope
    
    // call_site() span: can interact with caller's scope
    let call_site_ident = format_ident!("local_var", span = Span::call_site());
    
    // mixed_site() span: Rust 2021 hygiene
    let mixed_site_ident = format_ident!("local_var", span = Span::mixed_site());
    
    // Span from input: inherits the hygiene of input
    let derived_ident = format_ident!("derived_{}", input_ident);
    // This uses input_ident's span
    
    // Example: generating a local variable
    let output = quote! {
        let #call_site_ident = 42;  // Accessible from caller
        let #mixed_site_ident = 42;  // Hygienic in 2021
    };
}

Span choice affects hygiene—whether generated identifiers can interact with caller's scope.

Raw Identifiers

use quote::format_ident;
use proc_macro2::Ident;
 
fn raw_identifiers() {
    // format_ident! handles Rust keywords
    // If the result would be a keyword, it's automatically made raw
    
    // This would normally create a keyword 'type'
    // format_ident! makes it r#type automatically
    let type_ident = format_ident!("type");
    // Actually creates: r#type
    
    // But Ident::new panics on keywords without r# prefix
    // let bad = Ident::new("type", Span::call_site());  // PANIC
    
    // Must explicitly use r# prefix with Ident::new
    let good = Ident::new("r#type", Span::call_site());
    
    // format_ident! handles this automatically
    let keywords = ["fn", "match", "type", "let"];
    for kw in keywords {
        let ident = format_ident!("{}", kw);  // Works!
        // Creates r#fn, r#match, r#type, r#let
    }
}

format_ident! automatically handles Rust keywords by making them raw identifiers.

Error Messages and Spans

use quote::format_ident;
use syn::parse_quote;
 
fn error_span_example() {
    // When errors occur, spans determine what the error highlights
    
    // Input with its own span
    let input: syn::Ident = parse_quote!(my_function);
    
    // Derived identifier inherits input's span
    let getter = format_ident!("get_{}", input);
    
    // If getter causes an error (e.g., duplicate definition),
    // the error will point to where `my_function` was written
    
    // This is better than call_site, which would point to macro
    
    // With Ident::new and call_site:
    let getter_call_site = Ident::new(&format!("get_{}", input), Span::call_site());
    // Error would point to the macro invocation, not source
}

Inheriting spans from input makes error messages point to the right location.

Complex Identifier Construction

use quote::{format_ident, quote};
 
fn complex_identifiers(struct_ident: &syn::Ident, field_names: &[&str]) {
    // Build identifiers from multiple sources
    
    // Struct-level derived names
    let builder_ident = format_ident!("{}Builder", struct_ident);
    let error_ident = format_ident!("{}Error", struct_ident);
    
    // Field-level derived names
    let field_idents: Vec<_> = field_names
        .iter()
        .map(|name| format_ident!("{}", name))
        .collect();
    
    // Indexed identifiers
    let indexed: Vec<_> = (0..5)
        .map(|i| format_ident!("item_{}", i))
        .collect();
    
    // Combined
    let tokens = quote! {
        struct #builder_ident {
            #( #field_idents: String, )*
        }
        
        impl #struct_ident {
            fn builder() -> #builder_ident {
                #builder_ident {
                    #( #field_idents: String::new(), )*
                }
            }
        }
    };
}

format_ident! excels at building families of related identifiers.

Performance Considerations

use quote::format_ident;
use proc_macro2::Ident;
use std::fmt::Write;
 
fn performance_comparison() {
    // Both are fast enough for proc macros
    // The difference is usually negligible
    
    // Ident::new: Single allocation for the identifier
    let ident1 = Ident::new("simple_name", proc_macro2::Span::call_site());
    
    // format_ident!: String formatting + allocation
    let ident2 = format_ident!("name_{}", 123);
    
    // In a tight loop (unusual in proc macros):
    // Ident::new might be slightly faster
    
    // But clarity usually matters more than micro-optimization
    // format_ident! is clearer for derived names
    
    // If you need maximum performance and have the string:
    let name = "derived_name";
    let ident = Ident::new(name, span);  // Skip formatting overhead
}

Performance differences are minimal; clarity matters more.

Debugging Generated Identifiers

use quote::format_ident;
use proc_macro2::Ident;
 
fn debugging() {
    let base = "value";
    let index = 42;
    
    // format_ident! produces identifiers that print normally
    let ident = format_ident!("{}_{}", base, index);
    println!("Generated identifier: {}", ident);  // "value_42"
    
    // Access the identifier string
    let ident_string = ident.to_string();
    assert_eq!(ident_string, "value_42");
    
    // Ident::new is the same
    let ident2 = Ident::new("value_42", proc_macro2::Span::call_site());
    assert_eq!(ident2.to_string(), "value_42");
}

Both produce identifiers with the same string representation.

Pattern Matching Generated Code

use quote::format_ident;
use syn::parse_quote;
 
fn pattern_example() {
    // Generated identifiers work in pattern matching
    
    let ident = format_ident!("MY_CONST");
    
    // If you generate:
    let generated = quote! {
        const #ident: i32 = 42;
    };
    
    // And later parse/pattern match:
    let item: syn::Item = parse_quote! {
        const #ident: i32 = 42;
    };
    
    // The identifier is syntactically identical to hand-written MY_CONST
    // It's not a "generated" identifier at runtime
    // It's just MY_CONST in the output
}

Generated identifiers become regular identifiers in the output code.

Common Patterns

use quote::{format_ident, quote};
use syn::parse_quote;
 
fn common_patterns() {
    // 1. Prefixed identifiers
    let getter = format_ident!("get_{}", "value");
    let setter = format_ident!("set_{}", "value");
    
    // 2. Suffixed identifiers
    let error_type = format_ident!("{}Error", "MyStruct");
    
    // 3. Indexed identifiers (for generated fields)
    let fields: Vec<_> = (0..10)
        .map(|i| format_ident!("field_{}", i))
        .collect();
    
    // 4. Uppercasing first letter (for types)
    let type_name = "my_type";
    let upper = format_ident!("{}{}", 
        type_name.chars().next().unwrap().to_uppercase(),
        &type_name[1..]
    );
    
    // 5. Building path segments
    let mod_name = format_ident!("generated_mod");
    let fn_name = format_ident!("generated_fn");
}

Common identifier derivation patterns are concise with format_ident!.

Synthesis

Comparison table:

Aspect Ident::new format_ident!
Syntax Ident::new(string, span) format_ident!("fmt", args...)
Formatting Manual Built-in
Span Required explicitly Inferred or explicit
Keywords Must use r# prefix Auto-escaped
Use case Known string, explicit span Derived identifiers
Ergonomics Verbose for formatting Concise

When to use Ident::new:

// Complete identifier string known
let ident = Ident::new("exact_name", span);
 
// Reading identifier from config/external source
let name = load_name_from_config();
let ident = Ident::new(&name, Span::call_site());
 
// Need precise span control with no interpolation
let ident = Ident::new("simple", specific_span);

When to use format_ident!:

// Building identifiers from input
let getter = format_ident!("get_{}", field_ident);
let builder = format_ident!("{}Builder", struct_name);
 
// Creating numbered identifiers
let field = format_ident!("field_{}", index);
 
// Combining multiple parts
let ident = format_ident!("{}_{}_{}", prefix, name, suffix);
 
// Automatic keyword handling
let type_ident = format_ident!("type");  // Becomes r#type

Key insight: format_ident! is the ergonomic choice for most identifier generation in proc macros. It handles string interpolation, inherits spans from interpolated values (improving error messages), and automatically escapes Rust keywords. Use Ident::new when you have a complete, ready-to-use identifier string and need explicit span control. The span inheritance in format_ident! is particularly valuable for error messages—when you derive get_value from a field named value, errors point to the field's location, not the macro definition site.