3

In Rust you can overload operators (+, -, /, *, +=, etc.). I have a simple add implementation for my Vec3 type:

use std::ops::Add;

struct Vec3 {
    e0: f32,
    e1: f32,
    e2: f32,
}

impl Add<f32> for &Vec3 {
    type Output = Vec3;

    fn add(self, other: f32) -> Vec3 {
        Vec3 {
            e0: self.e0 + other,
            e1: self.e1 + other,
            e2: self.e2 + other,
        }
    }
}

And I can use it by doing:

let result = my_vec_3 + 43f32;

But doing it the other way errors out:

let this_wont_compile = 43f32 + my_vec_3;
error[E0277]: cannot add `Vec3` to `f32`
  --> src/lib.rs:23:35
   |
23 |     let this_wont_compile = 43f32 + my_vec_3;
   |                                   ^ no implementation for `f32 + Vec3`
   |
   = help: the trait `std::ops::Add<Vec3>` is not implemented for `f32`

I know I could write an implementation for impl Add<&Vec3> for f32 but that's what I'm looking to automate.

How can you write your implementation such that the LHS and RHS are interchangeable? Is it possible in Rust?

Nathan Lafferty
  • 1,988
  • 1
  • 15
  • 17
  • Assuming I want to implement add, mult, divide, sub for reference and ownership (&Vec, Vec) means 8 functions. Doing all of that twice for LHS and RHS means 16. Its not hard, just boilerplate. – Nathan Lafferty Mar 20 '19 at 17:17
  • 2
    You could probably write a [macro](https://doc.rust-lang.org/book/macros.html) that expands to both implementations – apetranzilla Mar 20 '19 at 17:19
  • @apemanzilla, wouldn't that require me to use the macro inline where I want to use it? `let result = vecadd!(0.5, my_vec_3)`? – Nathan Lafferty Mar 20 '19 at 17:27
  • Example of implementing numeric traits with a macro: https://github.com/servo/euclid/blob/19a833debdd272ca90d90134f469ca9020a2e9b3/src/num.rs#L43-L58 – Peter Hall Mar 20 '19 at 17:38
  • 2
    See also [How can I implement an operator like Add for a reference type so that I can add more than two values at once?](https://stackoverflow.com/q/45174458/155423) – Shepmaster Mar 20 '19 at 17:45
  • *I could write an implementation for `impl Add<&Vec3> for f32`* — That would only allow `43f32 + &my_vec_3` – Shepmaster Mar 20 '19 at 17:52
  • @Shepmaster, I'm thinking through an implementation involving macros. So far I think I need to define a macro for each operation (Add, Mult, AddAssign, SubAssign etc.). Each macro would expand providing three * four implementations. (Add for Vec3, Add for Vec3, Add for f32; and for $lhs, $rhs; for &$lhs, $rhs; for $lhs, &$rhs; for &$lhs, &$lhs). Am I on the right track? Is it possible to use the rust source macros directly or will I need to copy them into my project? – Nathan Lafferty Mar 20 '19 at 18:21
  • 2
    @NathanLafferty macros are importable, no need to copy their definitions. But if you use a macro to generate trait implementations, you don't have to expose the macro - impls are there already, so the user of your lib (or you in a different project depending on it) has no use for the macro. – Michail Mar 20 '19 at 18:47
  • 1
    To add to Michail's comment, if you use macros internally to your crate to implement `Add`, your users will not need to know that and can not import the macros. However, you cannot import the macros that the Rust standard library uses to implement their own code for the same reason. In either case, the macros would need to be deliberately exported, if that were desired for some reason. – Shepmaster Mar 20 '19 at 20:27
  • 2
    @Nathan you do not need to define macro for each operation. You can call macros from macros, etc. [Here's an example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cda956155176bae5ba5309b1aab451a8) you can start with. – zrzka Mar 20 '19 at 23:07
  • @zrzka, it looks like that playground contains everything I'd hope for (sans generating the owner/reference cases, but that's easy now that I understand how it works thanks to Shepmaster!). I was going to look at implementing it tomorrow. If you made it more generic ("You can't do what you want directly, but you can define a macro to automate the boilerplate") I would "accept" the answer. Or I can submit an answer myself :) -- Thanks everyone! – Nathan Lafferty Mar 20 '19 at 23:46
  • @Nathan I can do it, but as you already have all the required info, feel free to finish it & answer your own question. As a bonus, you can earn Self-Learner badge :) – zrzka Mar 21 '19 at 09:01

1 Answers1

4

The language does not automate this for you.

Your options are:

  1. Write the implementations yourself
  2. Build a macro and use metaprogramming

zrzka was kind enough to construct a Rust Playground that provides example macros for this specific use-case.

This question also provides some helpful hints since the Rust Source Code itself uses these macros to automate some of the tedium.

One concern I had was if I used a macro I would have to call it inline (like vec![]). Since macros are expanded at compile time, your macro will generate your functions for you that you can call normally. RLS will still provide syntax support and everything will work as you expect it should.

Here's the implementation I ended up going with. I'm sure much more could be automated (forward_ref_binop for one) but I'm happy with it.

/// Generates the operations for vector methods. `let result = my_vec_3 + my_other_vec3`
/// Handles `Vec3, Vec3`, `Vec3, &Vec3`, `&Vec3, Vec3`, `&Vec3, &Vec3`
/// `vec3_vec3_op(ops::AddAssign, add_assign)` (note the camelcase add_assign name)
macro_rules! vec3_vec3_op {
    ($($path:ident)::+, $fn:ident) => {
        impl $($path)::+<Vec3> for Vec3 {
            type Output = Vec3;

            fn $fn(self, other: Vec3) -> Self::Output {
                Vec3 {
                    e0: self.e0.$fn(other.e0),
                    e1: self.e1.$fn(other.e1),
                    e2: self.e2.$fn(other.e2),
                }
            }
        }

        impl $($path)::+<&Vec3> for &Vec3 {
            type Output = Vec3;

            fn $fn(self, other: &Vec3) -> Self::Output {
                Vec3 {
                    e0: self.e0.$fn(other.e0),
                    e1: self.e1.$fn(other.e1),
                    e2: self.e2.$fn(other.e2),
                }
            }
        }

        impl $($path)::+<&Vec3> for Vec3 {
            type Output = Vec3;

            fn $fn(self, other: &Vec3) -> Self::Output {
                Vec3 {
                    e0: self.e0.$fn(other.e0),
                    e1: self.e1.$fn(other.e1),
                    e2: self.e2.$fn(other.e2),
                }
            }
        }

        impl $($path)::+<Vec3> for &Vec3 {
            type Output = Vec3;

            fn $fn(self, other: Vec3) -> Self::Output {
                Vec3 {
                    e0: self.e0.$fn(other.e0),
                    e1: self.e1.$fn(other.e1),
                    e2: self.e2.$fn(other.e2),
                }
            }
        }
    };
}

/// Generates the operations for vector method assignment. `my_vec += my_other_vec`
/// Handles `Vec3, Vec3` and `Vec3, &Vec3`
/// `vec3_vec3_opassign(ops::AddAssign, add_assign)` (note the camelcase add_assign name)
macro_rules! vec3_vec3_opassign {
    ($($path:ident)::+, $fn:ident) => {
        impl $($path)::+<Vec3> for Vec3 {
            fn $fn(&mut self, other: Vec3) {
                self.e0.$fn(other.e0);
                self.e1.$fn(other.e1);
                self.e2.$fn(other.e2);
            }
        }

        impl $($path)::+<&Vec3> for Vec3 {
            fn $fn(&mut self, other: &Vec3) {
                self.e0.$fn(other.e0);
                self.e1.$fn(other.e1);
                self.e2.$fn(other.e2);
            }
        }
    };
}

/// Generates the operations for method assignment. `my_vec += f32`
/// `vec3_opassign(ops:AddAssign, add_assign)` (note the camelcase add_assign name)
macro_rules! vec3_opassign {
    ($($path:ident)::+, $fn:ident, $ty:ty) => {
        impl $($path)::+<$ty> for Vec3 {
            fn $fn(&mut self, other: $ty) {
                self.e0.$fn(other);
                self.e1.$fn(other);
                self.e2.$fn(other);
            }
        }
    }
}

/// Generates the operations for the method. `let result = my_vec + 4f32`
/// Handles `Vec3, T`, `T, Vec3`, `&Vec3, T`, `T, &Vec3`
/// `vec3_op!(ops:Add, add, f32)`
macro_rules! vec3_op {
    ($($path:ident)::+, $fn:ident, $ty:ty) => {
        // impl ops::Add::add for Vec3
        impl $($path)::+<$ty> for Vec3 {
            type Output = Vec3;

            // fn add(self, other: f32) -> Self::Output
            fn $fn(self, other: $ty) -> Self::Output {
                Vec3 {
                    // e0: self.e0.add(other)
                    e0: self.e0.$fn(other),
                    e1: self.e1.$fn(other),
                    e2: self.e2.$fn(other),
                }
            }
        }

        impl $($path)::+<$ty> for &Vec3 {
            type Output = Vec3;

            fn $fn(self, other: $ty) -> Self::Output {
                Vec3 {
                    e0: self.e0.$fn(other),
                    e1: self.e1.$fn(other),
                    e2: self.e2.$fn(other),
                }
            }
        }

        impl $($path)::+<Vec3> for $ty {
            type Output = Vec3;

            fn $fn(self, other: Vec3) -> Self::Output {
                Vec3 {
                    e0: self.$fn(other.e0),
                    e1: self.$fn(other.e1),
                    e2: self.$fn(other.e2),
                }
            }
        }

        impl $($path)::+<&Vec3> for $ty {
            type Output = Vec3;

            fn $fn(self, other: &Vec3) -> Self::Output {
                Vec3 {
                    e0: self.$fn(other.e0),
                    e1: self.$fn(other.e1),
                    e2: self.$fn(other.e2),
                }
            }
        }
    }
}

macro_rules! vec3_op_for {
    ($ty: ty) => {
        vec3_op!(ops::Add, add, $ty);
        vec3_op!(ops::Sub, sub, $ty);
        vec3_op!(ops::Mul, mul, $ty);
        vec3_op!(ops::Div, div, $ty);
        vec3_opassign!(ops::AddAssign, add_assign, $ty);
        vec3_opassign!(ops::SubAssign, sub_assign, $ty);
        vec3_opassign!(ops::MulAssign, mul_assign, $ty);
        vec3_opassign!(ops::DivAssign, div_assign, $ty);
    };
}

vec3_vec3_op!(ops::Add, add);
vec3_vec3_op!(ops::Sub, sub);
vec3_vec3_op!(ops::Mul, mul);
vec3_vec3_op!(ops::Div, div);
vec3_vec3_opassign!(ops::AddAssign, add_assign);
vec3_vec3_opassign!(ops::SubAssign, sub_assign);
vec3_vec3_opassign!(ops::MulAssign, mul_assign);
vec3_vec3_opassign!(ops::DivAssign, div_assign);
vec3_op_for!(f32);

From here if I expanded my Vec3 class to handle generics, it would be trivial to add additional types.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Nathan Lafferty
  • 1,988
  • 1
  • 15
  • 17