You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 16:05:56