Loading pageā¦
Rust walkthroughs
Loading pageā¦
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.