Search code examples
rustrust-proc-macros

How do I make my custom derive macro accept trait generic parameters?


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
           } ...

Solution

  • 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`
    }