Is there a good way to combine all operator overloading combinations into a single trait in Rust? As discussed, in the question How to implement idiomatic operator overloading for values and references in Rust?, there are four different combinations for operator overloading binary operators: (ref,ref), (ref,val), (val,ref), and (val,val). Further, there's a number of macros and such for helping define these routines. My question is how to require all of these combinations in a sane way through a trait or another mechanism. Consider the following code that attempts to create an algebra for a general float/real type:
// Create some random type that we want to represent as a Real
#[derive(Debug,Clone)]
struct Foo <Real> {
x : Real,
y : Real,
}
// Add the algebra for Foo
impl <Real> std::ops::Add <&'_ Foo<Real>> for &'_ Foo <Real>
where
for <'a> &'a Real : std::ops::Add<&'a Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : &'_ Foo <Real>) -> Self::Output {
Foo {
x : &self.x + &other.x,
y : &self.y + &other.y,
}
}
}
impl <Real> std::ops::Add <Foo<Real>> for &'_ Foo <Real>
where
for <'a> &'a Real : std::ops::Add<Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : Foo <Real>) -> Self::Output {
Foo {
x : &self.x + other.x,
y : &self.y + other.y,
}
}
}
impl <Real> std::ops::Add <&'_ Foo<Real>> for Foo <Real>
where
for <'a> Real : std::ops::Add<&'a Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : &'_ Foo <Real>) -> Self::Output {
Foo {
x : self.x + &other.x,
y : self.y + &other.y,
}
}
}
impl <Real> std::ops::Add <Foo<Real>> for Foo <Real>
where
Real : std::ops::Add<Real,Output=Real>
{
type Output = Foo <Real>;
fn add(self, other : Foo <Real>) -> Self::Output {
Foo {
x : self.x + other.x,
y : self.y + other.y,
}
}
}
// Compute a function on a slice of Reals. This should work for f64 and Foo <f64>
fn foo <Real> (x : &[Real]) -> Real
where
for <'a> &'a Real :
std::ops::Add<&'a Real,Output=Real> +
std::ops::Add<Real,Output=Real> +
Clone,
for <'a> Real :
std::ops::Add<&'a Real,Output=Real> +
std::ops::Add<Real,Output=Real> +
Clone,
Real : Clone
{
(&x[0]+x[1].clone())+&x[2]
}
// Run foo on two different types
fn main() {
let x = vec![1.2,2.3,3.4];
let _x = foo::<f64>(&x);
println!("{:?}",_x);
let y : Vec <Foo<f64>>= x.into_iter().map(|z|Foo{x:z,y:z+1.0}).collect();
let _y = foo::<Foo<f64>>(&y);
println!("{:?}",_y);
}
The routine foo
places a requirement both on Real
and its reference for <'a> &'a Real
to complete the algebra. Now, this is pretty verbose and will only get worse as we add Mul
, Div
, etc. I'd like to either have a single trait for Real
or at least a single trait for Real
and for <'a> &'a Real
. That said, I can't quite figure out a definition for it. Typically, I'd place all of the definitions in a new trait such as:
trait MyFloat :
std::ops::Add <REFSELF,Output = NOREFSELF> +
std::ops::Add <NOREFSELF,Output = NOREFSELF>
where
Self : std::marker::Sized,
{}
impl <T> MyFloat for T where T:
std::ops::Add <REFSELF,Output = NOREFSELF> +
std::ops::Add <NOREFSELF,Output = NOREFSELF>
{}
But, this is already getting odd. We need the output to always be the non-reference form of the type and we want the right hand side to be either the reference form or the non-reference form of the type. However, as far as I know, there's not a mechanism for achieving this by manipulating Self
.
Is there a good way for combining these definitions into a single trait?