【问题标题】:Can a procedural macro derive on a struct add other derives?可以在结构上添加其他派生的过程宏派生吗?
【发布时间】:2018-07-12 16:40:27
【问题描述】:

程序宏派生是否可以将来自其他 crate 的派生添加到派生它的结构?

lib.rs

#[derive(Combined)]
struct Foo;

derive_combined.rs

#[macro_use] extern crate quote;
extern crate proc_macro2;
extern crate proc_macro;
extern crate syn;

use proc_macro::TokenStream;

#[proc_macro_derive(Combined)]
pub fn my_macro(input: TokenStream) -> TokenStream {
    let input: DeriveInput = syn::parse(input).unwrap();
    let ident = input.ident;
    let expanded = quote! {
        #[derive(Clone, Debug)]
        struct #ident;
    };

    expanded.into()
}

【问题讨论】:

  • 这段代码有什么问题?
  • @PeterHall 此代码故意不起作用。您不能复制结构的定义。

标签: macros rust


【解决方案1】:

虽然这不能用proc_macro_derive 方便地完成,但可以用proc_macro_attribute 完成,并且看到另一个答案已经使用了派生属性,这个解决方案可能更适合您的用例:

extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
extern crate syn;

use proc_macro2::TokenStream;

#[proc_macro_attribute]
pub fn add_derive(_metadata: proc_macro::TokenStream, input: proc_macro::TokenStream)
                 -> proc_macro::TokenStream {
    let input: TokenStream = input.into();
    let output = quote! {
        #[derive(Debug, Serialize, Deserialize, etc, ...)]
        #input
    };
    output.into()
}

然后使用这个宏:

#[add_derive]   
pub struct TestStruct { ... }

值得注意的是,属性宏替换令牌流,而派生宏适合附加到令牌流:Rust Reference: Procedural Macros

【讨论】:

    【解决方案2】:

    有一些次优的解决方法 - 是的!

    我在实现它时遇到的第一个问题是结构的重复定义 - 拥有多个定义是行不通的。为了解决这个问题,我使用了一个必须指定的自定义属性,该属性将是生成代码中结构的名称:

    #![feature(custom_attribute)]
    #[macro_use] extern crate quote;
    extern crate proc_macro;
    extern crate proc_macro2;
    extern crate syn;
    
    use syn::DeriveInput;
    use proc_macro::TokenStream;
    use proc_macro2::{Ident, Span};
    use syn::{Attribute, Meta, Lit};
    
    #[proc_macro_derive(Combined)]
    #[attribute(ActualName)]
    pub fn my_macro(input: TokenStream) -> TokenStream {
        let mut input: DeriveInput = syn::parse(input).unwrap();
    
        for attr in input.attrs.iter().map(Attribute::interpret_meta).filter(|x| x.is_some()).map(|x| x.unwrap()) {
            if &attr.name().to_string()[..] != "ActualName" { continue }
            let name;
            match attr {
                Meta::Word(ident) => { panic!("ActualName must be a name-value pair (i.e. #[ActualName = \"hey\"])"); },
                Meta::List(_) => { panic!("ActualName must be a name-value pair (i.e. #[ActualName = \"hey\"])"); },
                Meta::NameValue(meta_name_value) => {
                    match meta_name_value.lit {
                        Lit::Str(lit_str) => { name = lit_str.value(); },
                        _ => { panic!("ActualName must be a string"); }
                    };
                }
            };
            input.ident = Ident::new(&name[..], Span::call_site());
            let expanded = quote! {
                #[derive(Clone, Debug)]
                #input
            };
    
            return expanded.into()  
        }
        panic!("You must specify the ActualName attribute (i.e. #[Derive(Combined),         ActualName = \"...\"]")
    
    }
    

    将此代码放入派生箱后,以下代码示例将起作用:

    #![feature(custom_attribute)]
    #[macro_use]
    extern crate derive_combined;
    
    #[derive(Combined)]
    #[ActualName = "Stuff"]
    struct Stuff_ {
        pub a: i32,
        pub b: i64,
    }
    
    fn main() {
        println!("{:?}", Stuff { a: 10, b: 10 }.clone());
    }
    

    如果您对实现此功能有任何疑问,this 是我遵循的教程。如果这没有帮助,请随时询问。

    【讨论】:

    • 这是一个解决方案,但我不接受它,因为重命名结构的要求是 API 中的一个痛点。
    猜你喜欢
    • 2012-11-22
    • 2017-03-17
    • 1970-01-01
    • 2010-10-09
    • 2020-08-23
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多