Rust开发:如何在quote!宏中转义结构体构造器大括号?
问题
我正在为自定义FromLeBytes trait编写derive宏,期望生成的实现中,结构体构造器会依次调用每个字段的from_le_bytes()方法。以下是当前的代码实现:
use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{ parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics, Index, }; #[proc_macro_derive(HeapSize)] pub fn derive_heap_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // Parse the input tokens into a syntax tree. let input = parse_macro_input!(input as DeriveInput); // Used in the quasi-quotation below as `#name`. let name = input.ident; // Add a bound `T: HeapSize` to every type parameter T. let generics = add_trait_bounds(input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); // Generate an expression to sum up the heap size of each field. let sum = heap_size_sum(&input.data); let expanded = quote! { // The generated impl. impl #impl_generics lestream::FromLeBytes for #name #ty_generics #where_clause { fn heap_size_of_children(&self) -> usize { #sum } } }; // Hand the output tokens back to the compiler. proc_macro::TokenStream::from(expanded) } // Add a bound `T: HeapSize` to every type parameter T. fn add_trait_bounds(mut generics: Generics) -> Generics { for param in &mut generics.params { if let GenericParam::Type(ref mut type_param) = *param { type_param.bounds.push(parse_quote!(lestream::FromLeBytes)); } } generics } // Generate an expression to sum up the heap size of each field. fn heap_size_sum(data: &Data) -> TokenStream { match *data { Data::Struct(ref data) => { match data.fields { Fields::Named(ref fields) => { // Expands to an expression like // // 0 + self.x.heap_size() + self.y.heap_size() + self.z.heap_size() // // but using fully qualified function call syntax. // // We take some care to use the span of each `syn::Field` as // the span of the corresponding `heap_size_of_children` // call. This way if one of the field types does not // implement `HeapSize` then the compiler's error message // underlines which field it is. An example is shown in the // readme of the parent directory. let q = quote! { Self { }; for field in fields.named { let item_name = field.ident.expect("macro only works with named fields"); let item_type = field.ty; quote! { let #item_name = #item_type::from_le_bytes() } } } _ => panic!("The FromLeBytes derive can only be applied to structs"), } } Data::Enum(_) | Data::Union(_) => unimplemented!(), } }
自定义FromLeBytes trait定义:
use std::fmt::{Display, Formatter}; #[derive(Debug)] pub enum Error { UnexpectedEndOfStream, } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::UnexpectedEndOfStream => write!(f, "unexpected end of stream"), } } } impl std::error::Error for Error {} pub trait FromLeBytes: Sized { fn from_le_bytes<T>(bytes: &mut T) -> Result<Self, Error> where T: Iterator<Item = u8>; }
例如,对如下结构体使用#[derive(FromLeBytes)]:
#[derive(FromLeBytes)] struct Foo { bar: u8, spamm: u16, }
期望生成的实现:
impl FromLeBytes for Foo { fn from_le_bytes<T>(bytes: &mut T) -> Result<Self, Error> where T: Iterator<Item = u8>, { Ok(Self { bar: u8::from_le_bytes(bytes)?, spamm: u16::from_le_bytes(bytes)? }) } }
但我搞不清楚怎么在quote!宏里处理结构体构造器的大括号,这是我第一次写宏,也可以接受quote!之外的工具建议。
解决方案
1. 修正宏入口与方法签名
原代码是从HeapSize宏的示例复制而来,首先要把宏入口改为#[proc_macro_derive(FromLeBytes)],并且实现FromLeBytes trait的from_le_bytes方法,而非错误的heap_size_of_children。
2. 正确生成字段初始化代码
quote!完全支持结构体构造器语法,无需手动拆分大括号。可以遍历每个命名字段,生成字段名: 类型::from_le_bytes(bytes)?的代码片段,再将所有片段收集到TokenStream中,最后嵌入到Self { ... }里。
3. 修复泛型约束与错误处理
确保泛型参数的约束正确对应FromLeBytes trait,生成的方法签名与trait完全匹配,包括使用?操作符传递错误。
修改后的完整代码
use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{ parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics, }; #[proc_macro_derive(FromLeBytes)] pub fn derive_from_le_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // 解析输入为语法树 let input = parse_macro_input!(input as DeriveInput); let name = input.ident; // 为每个泛型参数添加FromLeBytes约束 let generics = add_trait_bounds(input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); // 生成结构体字段的初始化代码 let fields_init = generate_fields_init(&input.data); let expanded = quote! { impl #impl_generics lestream::FromLeBytes for #name #ty_generics #where_clause { fn from_le_bytes<T>(bytes: &mut T) -> Result<Self, lestream::Error> where T: Iterator<Item = u8>, { Ok(Self { #fields_init }) } } }; proc_macro::TokenStream::from(expanded) } // 为泛型参数添加FromLeBytes约束 fn add_trait_bounds(mut generics: Generics) -> Generics { for param in &mut generics.params { if let GenericParam::Type(ref mut type_param) = *param { type_param.bounds.push(parse_quote!(lestream::FromLeBytes)); } } generics } // 生成结构体字段的初始化代码片段 fn generate_fields_init(data: &Data) -> TokenStream { match *data { Data::Struct(ref data) => { match data.fields { Fields::Named(ref fields) => { let mut field_tokens = Vec::new(); for field in &fields.named { let ident = field.ident.as_ref().expect("只支持命名字段的结构体"); let ty = &field.ty; // 为每个字段生成: 字段名: 类型::from_le_bytes(bytes)? let field_init = quote_spanned! { field.span() => #ident: #ty::from_le_bytes(bytes)?, }; field_tokens.push(field_init); } quote! { #(#field_tokens)* } } Fields::Unnamed(_) => panic!("FromLeBytes derive不支持元组结构体"), Fields::Unit => panic!("FromLeBytes derive不支持单元结构体"), } } Data::Enum(_) | Data::Union(_) => unimplemented!("FromLeBytes derive仅支持结构体"), } }
关键说明
quote_spanned!的使用:保留字段的原始代码跨度,当字段类型未实现FromLeBytes时,编译器会精准提示对应的字段位置。- 字段初始化代码收集:遍历所有命名字段生成单个字段的初始化片段,再用
#(#field_tokens)*语法将所有片段展开到结构体构造器中。 - 方法签名匹配:确保生成的
from_le_bytes方法的泛型约束、参数、返回值都和trait定义完全一致,包括正确引用lestream::Error。
内容的提问来源于stack exchange,提问作者user22747722
相关产品推荐
相关产品推荐

