12

I am trying to implement custom derive macros for my traits, and they actually work!

However I have a slight problem. I can't seem to find a way to include generic parameters to the trait.

Specifically, I want to do something like this : #[derive(MyCustomDerive<'a, B, C>)]

Instead, right now I am hard-coding the generics, like so :

let gen = quote! {
        impl #impl_generics Graph<'a, V, E> for #name #ty_generics #where_clause {
            fn Map(&self) -> &MAP<V, E> {
                &self.map
            }
            ...
}

As you can see, I am including 'a, V and E fixed within the quote block, instead of something I want to achieve, which is being able to flexibly derive the trait with the generic types I want.

What I would like is something akin to this :

#[derive(MyCustomDerive<'a, B, C>)]

to result in something equivalent to this

let gen = quote! {
        impl #impl_generics Graph<'a, B, C> for #name #ty_generics #where_clause {
            fn Map(&self) -> &MAP<B, C> {
                &self.map
            }
            ...
}

This would allow me to reserve (of course if necessary) V and E for other things and in my opinion make code more controllable. Thank you for your help!

Update 1 : This is how my derive function looks

pub fn derive(ast: &syn::DeriveInput) -> TokenStream {
   let name = &ast.ident;
   let generics = &ast.generics;
   let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
   let gen = quote! {
       impl #impl_generics Graph<'a, V, E> for #name #ty_generics #where_clause {
           fn Map(&self) -> &MAP<V, E> {
            &self.map
           } ...

1 Answers1

21

I don't believe it's possible to use exactly the syntax you describe in your post (#[derive(MyCustomDerive<'a, B, C>)]). However, consider the following syntax that used an additional custom attribute instead:

#[derive(MyTrait)]
#[my_trait('a, B, C)]
struct MyStruct {
    // ...
}

To allow the my_trait attribute to be used, you'll have to add an attributes section to your proc_macro_derive attribute.

#[proc_macro_derive(MyTrait, attributes(my_trait))]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
    // ...
}

For help with parsing the attribute itself, take a look at syn::Attribute. The tokens field is a TokenStream from which you could extract the necessary parameters. For example, if your trait has one lifetime and two type parameters, your parsing logic might look something like this:

struct MyParams(syn::Lifetime, syn::Ident, syn::Ident);
impl syn::Parse for MyParams {
    fn parse(input: syn::ParseStream) -> Result<Self> {
        let content;
        syn::parenthesized!(content in input);
        let lifetime = content.parse()?;
        content.parse::<Token![,]>()?;
        let type1 = content.parse()?;
        content.parse::<Token![,]>()?;
        let type2 = content.parse()?;
        Ok(MyParams(lifetime, type1, type2))
    }
}

pub fn derive(ast: &syn::DeriveInput) -> TokenStream {
    let attribute = ast.attrs.iter().filter(
        |a| a.path.segments.len() == 1 && a.path.segments[0].ident == "my_trait"
    ).nth(0).expect("my_trait attribute required for deriving MyTrait!");

    let parameters: MyParams = syn::parse2(attribute.tokens.clone()).expect("Invalid my_trait attribute!");
    // ... do stuff with `parameters`
}
jgrowl
  • 2,117
  • 2
  • 17
  • 23
AlphaModder
  • 3,266
  • 2
  • 28
  • 44
  • Thank you very much for this answer! I've been tearing my hair out for a day now :) You're a life saver! Couldn't find much documentation about attribute macros, this is awesome! –  May 19 '19 at 18:04
  • 1
    @StefanChircop Glad I could help! Procedural macros are a relatively new language feature, so the documentation is pretty sparse right now. Browsing the source of other procedural macro crates is a good way to learn how to do stuff right now. (As is asking here, of course!) – AlphaModder May 19 '19 at 20:43
  • If I may ask, using your implementation, I get the following error : `14 | #[GraphStruct<'a, V, E, map, inc, out, sen, rec, nodes, edges>] | ^ expected one of `(`, `::`, `=`, `[`, `]`, or `{` here` If I swap `<>` for `()` in the attribute, I instead get this : `Invalid GraphStruct attribute!: Error("expected `<`")` when trying to parse the arguments. However '(' is not allowed in use with 'Token!', so I'm lost here –  May 22 '19 at 12:49
  • 1
    @StefanChircop Ah, evidently rust still has some restrictions on what can be present at that point in an attribute. Since you don't seem to mind using parentheses, I've updated my answer to show how to parse them. – AlphaModder May 23 '19 at 10:28
  • Thank you a lot for your patience :) –  May 23 '19 at 23:51
  • @StefanChircop No problem, it was my bad for not making sure the angle brackets would work. – AlphaModder May 24 '19 at 00:44