20

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()
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
HiDefender
  • 2,088
  • 2
  • 14
  • 31
  • Note that the answer you reference is about `macro_rules` macros; I don't know that it applies to procedural macros; you should try the procedural macro route. – Shepmaster Jun 04 '19 at 20:08
  • 1
    You could probably also do something like `my_derive! { struct Foo(i32) }` which would add all the derives, but that wouldn't create a *new* derive. – Shepmaster Jun 04 '19 at 20:14
  • 2
    I'm not sure it's a good thing to do. – Stargateur Jun 04 '19 at 20:18
  • @Shepmaster Procedural macro route doesn't work either. I thought about creating the struct inside of the derive procedural macro (It does work that way.), but I feel like it would make the code less maintainable. Someone else, would not be able to quickly see where the struct is defined. – HiDefender Jun 04 '19 at 21:01
  • @Stargateur Why is that? – HiDefender Jun 04 '19 at 21:04
  • 1
    @Shepmaster Something like [so](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5198e168ccd6ae52c52f3fc1f0bb03da)? – Optimistic Peach Jun 04 '19 at 22:08
  • In your situation, I would ask the `derive_more` team to add new derives, like `Int` and `Float` that expand to this – Boiethios Jun 05 '19 at 09:06

1 Answers1

14

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); }
Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54