可以在结构上派生的过程宏加上其他派生吗?

时间:2018-07-12 16:40:28

标签: macros rust

程序宏派生是否有可能将其他板条箱的派生添加到派生它的结构上?

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()
}

2 个答案:

答案 0 :(得分:3)

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

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

#![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。如果那样也无济于事。

答案 1 :(得分:2)

虽然使用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 { ... }

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