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#typeKey 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.
