【问题标题】:How to implement a procedural macro which implements a generic trait for generic enums?如何实现一个为泛型枚举实现泛型特征的过程宏?
【发布时间】:2017-02-28 12:49:58
【问题描述】:

如何为这样的枚举实现过程宏?

#[derive(Copy, Clone, Debug, MyProcMacro)]
enum Enum<W, C, I, F> {
    A(W),
    B(C),
    C(I),
    D(F)
}

我尝试使用syn::Generics,但它无法编译并产生无效代码。这是我想要实现的特征:

pub trait MyTrait<S> {
    fn change(&mut self, new_obj: S) -> bool;
}

及实施:

#[proc_macro_derive(MyProcMacro)]
pub fn my_proc_macro(input: TokenStream) -> TokenStream {
    // Construct a string representation of the type definition
    let s = input.to_string();

    // Parse the string representation
    let ast = syn::parse_derive_input(&s).unwrap();

    // Build the impl
    let gen = impl_macro(&ast);

    // Return the generated impl
    gen.parse().unwrap()
}

fn impl_macro(ast: &syn::DeriveInput) -> Tokens {
    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

quote! {
    impl #impl_generics mycrate::MyTrait<#name #ty_generics> for #name #ty_generics #where_clause {
        fn change(&mut self, new_obj: #name #ty_generics) -> bool {
            true
        }
    }
}

它给出了这个代码:

impl < W , C , I , F > mycrate :: MyTrait < Enum < W , C , I , F > > for Enum < W , C , I , F > { 
    fn change ( & mut self , new_obj : Enum < W , C , I , F > ) -> bool {
        true 
    }
}

我觉得应该是这样的:

impl MyTrait<Enum<u64, u64, u64, u64>> for Enum<u64, u64, u64, u64> {
    fn change(&mut self, new_obj: Enum<u64, u64, u64, u64>) {
        true
    }
}

据我了解,我们无法从过程宏上下文中获取有关所需类型的信息,对吗?我想这就是我在syn crate 中找不到此类信息的原因。

如果我不修改我编写的代码,我会收到此错误:

error[E0382]: use of moved value: `new_obj`
  --> src/main.rs:28:30
   |
28 | #[derive(Copy, Clone, Debug, MyProcMacro)]
   |                              ^^^^^^^^^^^ value moved here in previous iteration of loop
   |
   = note: move occurs because `new_obj` has type `Enum<W, C, I, F>`, which does not implement the `Copy` trait

这个错误在我看来很奇怪,因为这个 enum 肯定派生了 Copy 特征。

更新:

根据@Matthieu M. 的评论,我能够通过向每个枚举类型添加Copy 要求来成功编译它:

enum CupState<W: Copy, C: Copy, I: Copy, F: Copy> { ... }

但是,我仍在寻找不需要用户代码操作的更好的解决方案。

【问题讨论】:

  • 这个错误对我来说看起来很奇怪,因为这个枚举肯定派生了 Copy trait。 => 实际上并非如此。在泛型上派生Copy, Clone, ... 仅在所有数据成员也实现该特征的条件下实现特征。因此,对于不受限制实现Copy 的泛型W,整个泛型枚举不会实现Copy
  • 那么我该如何解决呢?通过将: Copy 添加到ty_generics 中的每个类型?
  • 如果我知道如何解决这个问题,我会发布答案,而不是评论。对不起:x

标签: generics enums macros rust


【解决方案1】:

如果您需要派生的自身类型来实现Copy,您可以让派生宏将绑定T: Copy 添加到生成的 impl 块中的每个类型参数。

extern crate proc_macro;
use self::proc_macro::TokenStream;

use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput, GenericParam, Generics};

#[proc_macro_derive(MyProcMacro)]
pub fn my_proc_macro(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);

    let name = &ast.ident;
    let generics = add_trait_bounds(ast.generics);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    TokenStream::from(quote! {
        impl #impl_generics MyTrait<Self> for #name #ty_generics #where_clause {
            fn change(&mut self, new_obj: Self) -> bool {
                true
            }
        }
    })
}

// Add a bound `T: Copy` 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!(Copy));
        }
    }
    generics
}

调用宏:

use my_proc_macro::MyProcMacro;

pub trait MyTrait<S> {
    fn change(&mut self, new_obj: S) -> bool;
}

#[derive(Copy, Clone, Debug, MyProcMacro)]
enum Enum<W, C, I, F> {
    A(W),
    B(C),
    C(I),
    D(F),
}

使用cargo expand我们可以确认生成的代码有Copy边界:

impl<W: Copy, C: Copy, I: Copy, F: Copy> MyTrait<Self> for Enum<W, C, I, F> {
    fn change(&mut self, new_obj: Self) -> bool {
        true
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-02
    • 1970-01-01
    • 2020-12-24
    • 1970-01-01
    • 2019-04-04
    相关资源
    最近更新 更多