Reputation: 2098
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?
Upvotes: 3
Views: 1469
Reputation: 2098
The language does not automate this for you.
Your options are:
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.
Upvotes: 4