Loading pageā¦
Rust walkthroughs
Loading pageā¦
quote::format_ident! help generate hygienic identifiers in procedural macros compared to string concatenation?quote::format_ident! generates identifiers with proper hygiene in procedural macros, ensuring they don't accidentally capture or conflict with identifiers in the macro caller's scope. String concatenation creates raw identifiers that can unintentionally shadow or conflict with user-defined names in the calling context. Hygiene in Rust macros determines whether generated identifiers refer to things defined within the macro expansion or in the scope where the macro is invoked. format_ident! produces identifiers that are properly scoped and won't accidentally capture user variables, while string concatenation creates identifiers that live in the caller's namespace and can cause unexpected shadowing or lookup conflicts.
use quote::{quote, format_ident};
use proc_macro2::Span;
fn main() {
// Generate identifier with proper hygiene
let var_name = format_ident!("my_var");
let expanded = quote! {
let #var_name = 42;
println!("{}", #var_name);
};
println!("{}", expanded);
// Generates: let my_var = 42; println!("{}", my_var);
}format_ident! creates identifiers that integrate with quote! for code generation.
use quote::quote;
use syn::Ident;
use proc_macro2::Span;
fn main() {
// String concatenation to create identifier
let name = "my_var";
let ident = Ident::new(name, Span::call_site());
let expanded = quote! {
let #ident = 42;
println!("{}", #ident);
};
println!("{}", expanded);
// Same output but different hygiene properties
}Ident::new() creates an identifier but doesn't handle hygiene automatically.
use quote::{quote, format_ident};
use proc_macro2::Span;
use syn::Ident;
// Hypothetical macro that generates a temporary variable
fn generate_with_temp_bad() -> proc_macro2::TokenStream {
// Using Ident::new - creates unhygienic identifier
let temp = Ident::new("temp", Span::call_site());
quote! {
let #temp = 42;
let result = #temp + 1;
}
}
fn generate_with_temp_good() -> proc_macro2::TokenStream {
// Using format_ident! - creates hygienic identifier
let temp = format_ident!("temp");
quote! {
let #temp = 42;
let result = #temp + 1;
}
}
fn main() {
// User code
let temp = 100;
// BAD: macro's temp shadows user's temp
// If unhygienic, the macro's temp would conflict
// GOOD: macro's temp is hygienic, doesn't conflict
let generated = generate_with_temp_good();
println!("{}", generated);
}Hygienic identifiers prevent accidental capture of user variables.
use quote::{quote, format_ident, IdentFragment};
use proc_macro2::Span;
use syn::Ident;
// In a real proc_macro, this would be:
// #[proc_macro]
// pub fn make_getter(input: TokenStream) -> TokenStream { ... }
fn demonstrate_hygiene() {
// Context: User has a variable named 'value'
let user_code = quote! {
let value = 100;
let result = {
// Macro expansion here
let inner = value + 1;
inner
};
};
println!("User code:\n{}", user_code);
// If macro generates 'value' internally:
// UNHYGIENIC (string-based):
let value_ident = Ident::new("value", Span::call_site());
let unhygienic = quote! {
let #value_ident = 0; // Shadows outer 'value'!
#value_ident + 1
};
// This 'value' would shadow the user's 'value'
// HYGIENIC (format_ident!):
let value_ident = format_ident!("value");
let hygienic = quote! {
let #value_ident = 0; // Different 'value' - hygienic
#value_ident + 1
};
// This 'value' is scoped to the macro expansion
println!("\nUnhygienic expansion:\n{}", unhygienic);
println!("\nHygienic expansion:\n{}", hygienic);
}
fn main() {
demonstrate_hygiene();
}Hygienic identifiers have different scope from user code even with same name.
use quote::{quote, format_ident};
fn main() {
let index = 5;
let prefix = "field";
// Format arguments like println!
let field_name = format_ident!("{}_{}", prefix, index);
let expanded = quote! {
struct MyStruct {
#field_name: i32,
}
};
println!("{}", expanded);
// Generates: struct MyStruct { field_5: i32, }
// With padding
let padded = format_ident!("var_{:0>3}", 7);
println!("{}", padded); // var_007
// With expressions
let count = 10;
let total = format_ident!("total_{}", count * 2);
println!("{}", total); // total_20
}format_ident! supports format strings like println!.
use quote::{quote, format_ident};
use proc_macro2::Span;
use syn::Ident;
fn main() {
// format_ident! uses call_site() span by default
let default_ident = format_ident!("my_var");
// Explicit span
let span = Span::call_site();
let explicit_ident = format_ident!("my_var", span = span);
// From existing ident's span
let existing: Ident = syn::parse_quote!(other_var);
let with_span = format_ident!("new_var", span = existing.span());
// The span affects error reporting location
// and hygiene in proc_macro context
}Spans control error reporting and hygiene scope.
// In a real proc_macro crate:
use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{parse_macro_input, DeriveInput};
// #[proc_macro_derive(Getters)]
// pub fn derive_getters(input: TokenStream) -> TokenStream {
// let input = parse_macro_input!(input as DeriveInput);
//
// let name = &input.ident;
// let fields = match &input.data {
// syn::Data::Struct(data) => match &data.fields {
// syn::Fields::Named(fields) => &fields.named,
// _ => panic!("Only named fields supported"),
// },
// _ => panic!("Only structs supported"),
// };
//
// let getters = fields.iter().map(|f| {
// let field_name = &f.ident;
// let field_type = &f.ty;
//
// // HYGIENIC getter name
// let getter_name = format_ident!("get_{}", field_name.as_ref().unwrap());
//
// quote! {
// pub fn #getter_name(&self) -> &#field_type {
// &self.#field_name
// }
// }
// });
//
// quote! {
// impl #name {
// #(#getters)*
// }
// }.into()
// }
fn demonstrate_derive() {
use quote::quote;
use quote::format_ident;
// Simulating the getter generation
let struct_name = syn::Ident::new("Person", proc_macro2::Span::call_site());
let field_name = format_ident!("name");
let field_type: syn::Type = syn::parse_quote!(String);
// Generate getter name from field
let getter_name = format_ident!("get_{}", field_name);
let expanded = quote! {
impl #struct_name {
pub fn #getter_name(&self) -> &#field_type {
&self.#field_name
}
}
};
println!("{}", expanded);
}Real proc macros use format_ident! to generate derived method names.
use quote::quote;
use syn::Ident;
use proc_macro2::Span;
fn main() {
// Problem 1: Manual span management
let name1 = Ident::new(&format!("field_{}", 1), Span::call_site());
let name2 = Ident::new(&format!("field_{}", 2), Span::mixed_site());
let name3 = Ident::new(&format!("field_{}", 3), Span::call_site());
// Different spans have different hygiene properties
// Easy to get wrong
// Problem 2: No compile-time checking
let invalid = Ident::new("", Span::call_site()); // Empty ident
// Compiles but generates invalid code
let keyword = Ident::new("fn", Span::call_site()); // Keyword
// May cause parsing issues
// Problem 3: Verbose
let complex = Ident::new(
&format!("{}_{}", "prefix", 100),
Span::call_site()
);
// format_ident! handles all these
use quote::format_ident;
let clean = format_ident!("field_{}", 1); // Correct span automatically
}String concatenation requires manual span handling and has no validation.
use quote::{quote, format_ident, IdentFragment};
use std::fmt::Display;
// Custom types can implement IdentFragment for use in format_ident!
#[derive(Debug)]
struct FieldType {
name: String,
type_name: String,
}
impl Display for FieldType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}_{}", self.name, self.type_name)
}
}
impl IdentFragment for FieldType {
// Can override span if needed
// fn span(&self) -> Span { ... }
}
fn main() {
let field = FieldType {
name: "count".to_string(),
type_name: "i32".to_string(),
};
// Custom type in format_ident!
let ident = format_ident!("get_{}", field);
println!("{}", ident); // get_count_i32
// Works with any IdentFragment implementor
let num = 42u8; // u8 implements IdentFragment
let numbered = format_ident!("var_{}", num);
println!("{}", numbered); // var_42
}IdentFragment enables custom types in format_ident!.
use quote::{quote, format_ident};
use proc_macro2::Span;
use syn::Ident;
fn demonstrate_phases() {
// Phase 1: Parsing
// Input spans are from user code
// Phase 2: Macro expansion
// Macro generates new tokens
// Phase 3: Hygiene resolution
// Determines which identifiers refer to which declarations
// call_site span: resolves in caller's scope
let caller_ident = Ident::new("var", Span::call_site());
// mixed_site span: resolves in macro's scope (Rust 2018+)
let mixed_ident = Ident::new("var", Span::mixed_site());
// format_ident! uses mixed_site by default in Rust 2018+
let format_ident = format_ident!("var");
// Result: format_ident is hygienic (won't capture caller's var)
}Different spans create different hygiene behaviors.
use quote::{quote, format_ident};
use proc_macro2::Span;
use syn::Ident;
fn main() {
// STRING CONCATENATION APPROACH
let name = format!("field_{}", 1);
let ident1 = Ident::new(&name, Span::call_site());
// Issues:
// - Manual span specification
// - No hygiene guarantees
// - Can conflict with user identifiers
// - More verbose
// format_ident! APPROACH
let ident2 = format_ident!("field_{}", 1);
// Benefits:
// - Automatic correct span
// - Hygienic by default
// - Won't capture user identifiers
// - Cleaner syntax
// - Supports format arguments
// - Works with IdentFragment types
let expanded = quote! {
let #ident1 = 1; // May capture user's field_1
let #ident2 = 2; // Hygienic, separate scope
};
}format_ident! is cleaner, safer, and more ergonomic.
use quote::{quote, format_ident};
// Common pattern: generating numbered identifiers
fn generate_array_accessors(n: usize) -> proc_macro2::TokenStream {
let accessors = (0..n).map(|i| {
// Each ident is hygienic and unique
let getter = format_ident!("get_{}", i);
let setter = format_ident!("set_{}", i);
let index = syn::Index::from(i);
quote! {
pub fn #getter(&self) -> i32 {
self.arr[#index]
}
pub fn #setter(&mut self, val: i32) {
self.arr[#index] = val;
}
}
});
quote! {
#(#accessors)*
}
}
fn main() {
let expanded = generate_array_accessors(3);
println!("{}", expanded);
// Generates get_0, set_0, get_1, set_1, get_2, set_2
// All hygienic
}Generating multiple related identifiers is a common proc macro pattern.
use quote::{quote, format_ident};
use proc_macro2::Span;
fn main() {
// Override span for specific use cases
let base: syn::Ident = syn::parse_quote!(user_defined);
// Inherit span from user input (for error reporting)
let derived = format_ident!("{}_extended", base);
// Uses base.span() automatically
// Explicit span override
let custom_span = format_ident!("my_ident", span = Span::call_site());
// Uses specified span
// When to override:
// - Error should point to user's code: use input span
// - Internal identifier: use call_site or mixed_site
}Span customization controls error reporting and hygiene scope.
Core distinction:
format_ident!: Creates hygienic identifiers with automatic span handlingHygiene mechanics:
format_ident! benefits:
format_ident!("var_{}", i))IdentFragmentIdent::new(&format!(...), span)String concatenation issues:
When hygiene matters:
When string concatenation might suffice:
Key insight: Hygiene prevents an entire class of macro bugs where macro-generated identifiers accidentally shadow or capture user identifiers. In Rust's macro system, hygiene is implemented through span trackingāthe span determines which namespace an identifier belongs to. format_ident! uses the correct span automatically, while Ident::new() requires manual specification and is error-prone. For procedural macros, always use format_ident! for generated identifiers unless you specifically want the identifier to resolve in the caller's scope.