Creating a macro to reduce boilerplate for implementing operator traits


#1

I’m attempting to implement a macro which will reduce the boilerplate involved with implementing the core operator traits (Add, Sub, Mul, Div, etc). However, I’m having a bit of trouble with the following section:

macro_rules! impl_operator {
    ($tr: ident, $method: ident) => {
        impl $tr<Prime> for Prime {
            type Output = Prime;
            #[inline]
            fn $method(self, rhs: Prime) -> Prime {
                Prime {
                    num: (self.num.$method(rhs.num))
                }
            }
        }

        impl<'b, 'a> $tr<&'b Prime> for &'a Prime {
            type Output = Prime;
            #[inline]
            fn $method(&'a self, rhs: &'b Prime) -> Prime {
                Prime {
                    num: (self.num.$method(rhs.num))
                }
            }
        }
    };
}

In the above example, the Prime type is a custom struct created in my program. The num member is another type that’s defined in an external library. That type already implements the traits that I want; my macro will simply piggy-back off of those implementations.

When implementing an operator trait where the types involves are all references, I receive the following compiler error (when testing, I attempted to implement the Add trait):

prime.rs:160:27: 160:31 error: cannot move out of borrowed content [E0507]
prime.rs:160                     num: (self.num.$method(rhs.num))
                                       ^~~~
prime.rs:167:1: 167:26 note: in this expansion of impl_operator! (defined in prime.rs)
prime.rs:160:44: 160:47 error: cannot move out of borrowed content [E0507]
prime.rs:160                     num: (self.num.$method(rhs.num))
                                                        ^~~
prime.rs:167:1: 167:26 note: in this expansion of impl_operator! (defined in prime.rs)
error: aborting due to 2 previous errors

Of course, the issue here is caused by the borrow checker not liking me attempting to consume a reference to the inputs. How can I go about solving this?


#2

This is a guess, but you should just be able to explicitly borrow the arguments to the operator:

use std::ops::Add;

struct Bjorn;

impl Add<Bjorn> for Bjorn {
    type Output = Bjorn;
    fn add(self, _: Self) -> Bjorn {
        Bjorn
    }
}

impl<'a> Add<&'a Bjorn> for &'a Bjorn {
    type Output = Bjorn;
    fn add(self, _: &'a Bjorn) -> Bjorn {
        Bjorn
    }
}

struct Spim(Bjorn);

impl<'a> Add<&'a Spim> for &'a Spim {
    type Output = Spim;
    fn add(self, rhs: &'a Spim) -> Spim {
        Spim((&self.0) + (&rhs.0))
    }
}

fn main() {}

Incidentally, I don’t suppose you could just use newtype_derive?


#3

Even when explicitly borrowing the arguments to the operator, I’m still receiving the cannot move out of borrow content error.


#4

Well, that code works for me, so without details of what you’re doing differently, I don’t know what else to suggest. Even if I use your macro directly (provided I fix the problems with it), it works fine.

macro_rules! impl_operator {
    ($tr: ident, $method: ident) => {
        // ...

        impl<'b, 'a> $tr<&'b Prime> for &'a Prime {
            type Output = Prime;
            #[inline]
            fn $method(self, rhs: &'b Prime) -> Prime {
                Prime {
                    num: ((&self.num).$method(&rhs.num))
                }
            }
        }
    };
}

#5

Ahah! I feel silly now. I thought that I had tried surrounding the &self.num part in parens, but apparently I didn’t. That works! Thank you for the help.