3

I have a Module trait and I would like to write a procedural macro that implements Add, Sub, and Mul for any implementation of the Module trait.

Given a struct Foo<S> that implements Module, the resulting code should look like this

impl <S, RHS: Module>  std::ops::Add<RHS> for Foo<S> {
    type Output = crate::modules::Add<Foo<S>, RHS>;

    fn add(self, rhs: RHS) -> Self::Output {
        crate::modules::Add::new(self, rhs)
    }
}

The problem is that both the struct and the Add trait have their own generic types. If this weren't the case, the macro code could look something like this

let name = &ast.ident;
let generics = &ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
    impl #impl_generics std::ops::Add<RHS> for #name #ty_generics #where_clause {
        type Output = crate::modules::Add<#name, RHS>;
        
        fn add(self, rhs: RHS) -> Self::Output {
            crate::modules::Add::new(self, rhs)
        }
    }
}

but then the RHS type is never defined. I have tried variations on <#impl_generics, RHS: Module> and the like, but none of them work.

Obviously a blanket implementation of Add for T: Module would be better, but this is impossible to do because foreign traits can't be implemented for type parameters. See here. A workaround for this would also solve my problem in this specific case, but it seems like what I'm asking about should be possible.

Is there any way to do this?

AlbertGarde
  • 369
  • 1
  • 13

2 Answers2

2

The solution is to handle the type parameters for Foo manually instead of using generics.split_for_impl(). A working version of my example above becomes.

let name = &ast.ident;
let generics = &ast.generics;
let type_params = generics.type_params();
let (_, ty_generics, where_clause) = generics.split_for_impl();
quote! {
    #ast // This is not a derive macro, sp this is necessary to keep the original struct definition.
    impl <#(#type_params),*, RHS: crate::modules::Module>  std::ops::Add<RHS> for #name #ty_generics #where_clause {
        type Output = crate::modules::Add<#name #ty_generics, RHS>;

        fn add(self, rhs: RHS) -> Self::Output {
            crate::modules::Add::new(self, rhs)
        }
    }
}

Where type_params is an iterator of the type parameters with bounds and the #(#type_params),*, creates a list of these type parameters.

AlbertGarde
  • 369
  • 1
  • 13
  • Shouldn't it be `#(#type_params,)*` instead of `#(#type_params),*,`? Something like: `impl<#(#lifetimes,)* #(#type_params,)* 'a, T> MyTrait<'a, T> for...` – brucify Jan 15 '23 at 14:40
0

I'm not sure why your macro code is not working, but an easier way would be to write a blanket impl for any T that implements Module, and avoid macros entirely:

impl<T, Rhs> std::ops::Add<Rhs> for T
where
    T: Module,
    Rhs: Module,
{
    type Output = crate::modules::Add<T, Rhs>;

    fn add(self, rhs: Rhs) -> Self::Output {
        crate::modules::Add::new(self, rhs)
    }
}

Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
  • This would definitely be the better solution, but I can't blanket impl `Add` for `Module`, without the specialization feature of Rust. The problem comes from the possibility of Add later being implemented for a struct also implementing Module, then there are two implementations. A workaround for _that_ would also be a solution for my problem. – AlbertGarde May 05 '21 at 18:11
  • Just realized that a blanket impl is actually impossible because of this error: https://doc.rust-lang.org/error-index.html#E0210 – AlbertGarde May 05 '21 at 18:24