繁体   English   中英

在过程派生 Rust 宏中列出结构字段的最简单方法是什么?

[英]What is the simplest way to list fields of a struct in procedural derive Rust macros?

我想为结构编写一个派生宏,它将生成另一个具有相同字段但包装在Option中的结构。

例如:

#[derive(Builder)]
pub struct Struct {
    field_1: String,
    field_2: Vec<String>,
    field_3: f32,
}

生成的结构:

pub struct StructBuilder {
    field_1: Option<String>,
    field_2: Option<Vec<String>>,
    field_3: Option<f32>,
}

我试过这样的事情:

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> TokenStream {
    let _input = parse_macro_input!(input as DeriveInput);
    let name = _input.ident;
    let name_builder = Ident::new(&format!("{}Builder", name.to_string()), Span::call_site());
    let Data::Struct(data) = _input.data;
    let mut fields  = proc_macro2::TokenStream::new();
    let mut expanded = proc_macro2::TokenStream::new();
    expanded.extend(quote!(pub struct #name_builder));
    for field in data.fields {
        let field_name = field.ident.unwrap();
        let field_type = field.ty;
        fields.extend(quote!(#field_name: Option<#field_type>,))
    }
    expanded.extend(TokenTree::from(Group::new(Delimiter::Brace, fields)));
    expanded.into()
}

但它甚至不编译。

所以我想知道两件事:

  1. 如何在 TokenStream 中添加新的代码行? 我不确定我是否正确使用extend方法。
  2. 如何在大括号{... }之间插入生成的字段? 也不确定我使用expanded.extend(TokenTree::from(Group::new(Delimiter::Brace, fields))); 正确。 这也不起作用:
expanded.extend(quote!(pub struct #name_builder {));
    ...
expanded.extend(quote!(}));

我很乐意收到任何反馈。 谢谢!

_input.data中提取data的语法似乎略有偏差。 让我们先解决这个问题。

let syn::Data::Struct(data) = _input.data else {
  unimplemented!()
};

我不推荐使用extend方法。 而是使用quote! 创建顶级TokenStream ,然后在适当的地方将其他令牌流嵌套到其中。

让我们遍历fields并为每个字段创建一个TokenStream

let fields = data.fields.iter().map(|f| {
  let name = &f.ident;
  let ty = &f.ty;
  quote! {
    #name: Option<#ty>
  }
});

我们现在可以将字段令牌流嵌套到我们的顶级expanded令牌 stream 中并返回它。

let expanded = quote! (
  pub struct #name_builder {
    #(#fields,)*
  }
);

expanded.into()

这是完整的 function 供参考:

#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: TokenStream) -> TokenStream {
    let _input = parse_macro_input!(input as DeriveInput);
    let name = _input.ident;
    let name_builder = Ident::new(&format!("{}Builder", name.to_string()), Span::call_site());

    let data = if let syn::Data::Struct(data) = _input.data {
        data
    } else {
        unimplemented!();
    };

    let fields = data.fields.iter().map(|f| {
        let name = &f.ident;
        let ty = &f.ty;
        quote! {
            #name: Option<#ty>
        }
    });

    let expanded = quote! (
        pub struct #name_builder {
            #(#fields,)*
        }
    );

    expanded.into()
}

输入:

#[derive(Builder)]
pub struct Struct {
    field_1: String,
    field_2: Vec<String>,
    field_3: f32,
}

Output:

pub struct Struct {
    field_1: String,
    field_2: Vec<String>,
    field_3: f32,
}
pub struct StructBuilder {
    field_1: Option<String>,
    field_2: Option<Vec<String>>,
    field_3: Option<f32>,
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM