Search code examples
rustmacrostraits

Is there a way to alias multiple derives as a single one?


When using the newtype pattern I often have lengthy derives:

extern crate derive_more;
use derive_more::*;

#[derive(Add, Sub, Mul, Div, ..., Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
struct Foo(i32);

Is there a way to shorten this to something like this:

#[derive(Num)]
struct Foo(i32);

Where Num is a derive macro?

I found this, but it seems like one can't expand macros in attributes. This answer discusses how attributes must be attached to items, ruling this out:

#[proc_macro_derive(Num)]
pub fn num_derive(_: TokenStream) -> TokenStream {
    let gen = quote! {
        #[derive(Add, Sub, Mul, Div, ..., Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
    };
    gen.into()
}

Solution

  • As you mentioned, emitted attributes must be attached to an item, making a proc-macro like this impossible:

    #[proc_macro_derive(Num)]
    pub fn num_derive(_: TokenStream) -> TokenStream {
        let gen = quote! {
            #[derive(Eq, PartialEq, Ord, PartialOrd)]
        };
        gen.into()
    }
    

    proc-macros also come with the hassle of having to be defined in a separate crate, so generating them, or creating simple ones for ergonomic reasons is not worth it.

    With proc-macros ruled out, we can look to macro_rules. They have the same restriction regarding attributes. However, it is possible to wrap an item definition in a proc-macro and attach attributes to it:

    macro_rules! derive_stuff {
         ($i:item) => {
            #[derive(Eq, PartialEq, Ord, PartialOrd)]
            $i
        }
    }
    
    derive_stuff! { struct Foo(i32); }
    

    Given this, we can create a macro that generates a macro like above:

    macro_rules! derive_alias {
        ($name:ident => #[derive($($derive:ident),*)]) => {
            macro_rules! $name {
                ($i:item) => {
                    #[derive($($derive),*)]
                    $i
                }
            }
        }
    }
    
    derive_alias! {
        derive_stuff => #[derive(Eq, PartialEq, Ord, PartialOrd)]
    }
    
    derive_stuff! { struct Foo(i32); }
    

    So I created a crate (derive_alias) that does exactly that:

    use derive_alias::derive_alias;
    use derive_more::*;
    
    derive_alias! {
        derive_num => #[derive(Add, Sub, Mul, Div, ..., Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
    }
    
    derive_num! { struct Foo(i32); }