Implement left operator for generic struct

I want to do some 3d graphics and for that purpose want to implement vector3d
I want this vector3d to be generic over the underlying type like f32, f64 and so on

This is what I do to have left multiplication operator with f32.
This is actually not what I need. I want the left part is the be any type compatible with float

use num::Float;
use std::ops::{Add, Sub, Mul, Div};

pub struct Vector3d<T = f32>
where T: Float + std::ops::Sub<Output = T> + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + std::ops::Div<Output = T>
{
    x : T,
    y : T,
    z : T,
}

impl Mul<Vector3d<f32>> for f32 {
    type Output = Vector3d<f32>;
    fn mul(self, rhs: Self::Output) -> Self::Output {
        Self::Output {x : rhs.x * self, y : rhs.y * self, z : rhs.z * self}
    }
}

also im curious how I can give this alias "Float + std::ops::Sub<Output = T> + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + std::ops::Div<Output = T>
{"

I think this is unfortunately impossible. Someone could go through the effort of implementing Float for their own type, e.g.

use std::num::FpCategory;
use std::ops::*;
use num::{One, Zero, Num, Float, NumCast, ToPrimitive};

#[derive(Clone, Copy, PartialOrd, PartialEq)]
struct MyFloat(f32);

impl One for MyFloat {
    fn one() -> Self { Self(1.0) }
}

impl Zero for MyFloat {
    fn zero() -> Self { Self(0.0) }
    fn is_zero(&self) -> bool { self.0.is_zero() }
    
}

impl Add for MyFloat {
    type Output = Self;
    fn add(self, other: Self) -> Self { Self(self.0 + other.0) }
}

impl Sub for MyFloat {
    type Output = Self;
    fn sub(self, other: Self) -> Self { Self(self.0 - other.0) }
}

/*
impl Mul for MyFloat {
    type Output = Self;
    fn mul(self, other: Self) -> Self { Self(self.0 * other.0) }
}
*/

impl Rem for MyFloat {
    type Output = Self;
    fn rem(self, other: Self) -> Self { Self(self.0 % other.0) }
}

impl Div for MyFloat {
    type Output = Self;
    fn div(self, other: Self) -> Self { Self(self.0 / other.0) }
}

impl Num for MyFloat {
    type FromStrRadixErr = <f32 as Num>::FromStrRadixErr;
    fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
        Ok(Self(f32::from_str_radix(str, radix)?))
    }
}

impl Neg for MyFloat {
    type Output = Self;
    fn neg(self) -> Self { Self(-self.0) }
}

impl Float for MyFloat {
    fn nan() -> Self { Self(f32::nan()) }
    fn infinity() -> Self { Self(f32::infinity()) }
    fn neg_infinity() -> Self { Self(f32::neg_infinity()) }
    fn neg_zero() -> Self { Self(f32::neg_zero()) }
    fn min_value() -> Self { Self(f32::min_value()) }
    fn min_positive_value() -> Self { Self(f32::min_positive_value()) }
    fn max_value() -> Self { Self(f32::max_value()) }
    fn is_nan(self) -> bool { self.0.is_nan() }
    fn is_infinite(self) -> bool { self.0.is_infinite() }
    fn is_finite(self) -> bool { self.0.is_finite() }
    fn is_normal(self) -> bool { self.0.is_normal() }
    fn classify(self) -> FpCategory { self.0.classify() }
    fn floor(self) -> Self { Self(self.0.floor()) }
    fn ceil(self) -> Self { Self(self.0.ceil()) }
    fn round(self) -> Self { Self(self.0.round()) }
    fn trunc(self) -> Self { Self(self.0.trunc()) }
    fn fract(self) -> Self { Self(self.0.fract()) }
    fn abs(self) -> Self { Self(self.0.abs()) }
    fn signum(self) -> Self { Self(self.0.signum()) }
    fn is_sign_positive(self) -> bool { self.0.is_sign_positive() }
    fn is_sign_negative(self) -> bool { self.0.is_sign_negative() }
    fn mul_add(self, a: Self, b: Self) -> Self { Self(self.0.mul_add(a.0, b.0)) }
    fn recip(self) -> Self { Self(self.0.recip()) }
    fn powi(self, n: i32) -> Self { Self(self.0.powi(n)) }
    fn powf(self, n: Self) -> Self { Self(self.0.powf(n.0)) }
    fn sqrt(self) -> Self { Self(self.0.sqrt()) }
    fn exp(self) -> Self { Self(self.0.exp()) }
    fn exp2(self) -> Self { Self(self.0.exp2()) }
    fn ln(self) -> Self { Self(self.0.ln()) }
    fn log(self, base: Self) -> Self { Self(self.0.log(base.0)) }
    fn log2(self) -> Self { Self(self.0.log2()) }
    fn log10(self) -> Self { Self(self.0.log10()) }
    fn max(self, other: Self) -> Self { Self(self.0.max(other.0)) }
    fn min(self, other: Self) -> Self { Self(self.0.min(other.0)) }
    fn abs_sub(self, other: Self) -> Self { Self(Float::abs_sub(self.0, other.0)) }
    fn cbrt(self) -> Self { Self(self.0.cbrt()) }
    fn hypot(self, other: Self) -> Self { Self(self.0.hypot(other.0)) }
    fn sin(self) -> Self { Self(self.0.sin()) }
    fn cos(self) -> Self { Self(self.0.cos()) }
    fn tan(self) -> Self { Self(self.0.tan()) }
    fn asin(self) -> Self { Self(self.0.asin()) }
    fn acos(self) -> Self { Self(self.0.acos()) }
    fn atan(self) -> Self { Self(self.0.atan()) }
    fn atan2(self, other: Self) -> Self { Self(self.0.atan2(other.0)) }
    fn sin_cos(self) -> (Self, Self) { let (s, c) = self.0.sin_cos(); (Self(s), Self(c)) }
    fn exp_m1(self) -> Self { Self(self.0.exp_m1()) }
    fn ln_1p(self) -> Self { Self(self.0.ln_1p()) }
    fn sinh(self) -> Self { Self(self.0.sinh()) }
    fn cosh(self) -> Self { Self(self.0.cosh()) }
    fn tanh(self) -> Self { Self(self.0.tanh()) }
    fn asinh(self) -> Self { Self(self.0.asinh()) }
    fn acosh(self) -> Self { Self(self.0.acosh()) }
    fn atanh(self) -> Self { Self(self.0.atanh()) }
    fn integer_decode(self) -> (u64, i16, i8) { self.0.integer_decode() }
}

impl NumCast for MyFloat {
    fn from<T: ToPrimitive>(x: T) -> Option<Self> { Some(Self(<f32 as NumCast>::from(x)?)) }
}

impl ToPrimitive for MyFloat {
    fn to_i64(&self) -> Option<i64> { self.0.to_i64() }
    fn to_u64(&self) -> Option<u64> { self.0.to_u64() }
    fn to_isize(&self) -> Option<isize> { self.0.to_isize() }
    fn to_i8(&self) -> Option<i8> { self.0.to_i8() }
    fn to_i16(&self) -> Option<i16> { self.0.to_i16() }
    fn to_i32(&self) -> Option<i32> { self.0.to_i32() }
    fn to_i128(&self) -> Option<i128> { self.0.to_i128() }
    fn to_usize(&self) -> Option<usize> { self.0.to_usize() }
    fn to_u8(&self) -> Option<u8> { self.0.to_u8() }
    fn to_u16(&self) -> Option<u16> { self.0.to_u16() }
    fn to_u32(&self) -> Option<u32> { self.0.to_u32() }
    fn to_u128(&self) -> Option<u128> { self.0.to_u128() }
    fn to_f32(&self) -> Option<f32> { self.0.to_f32() }
    fn to_f64(&self) -> Option<f64> { self.0.to_f64() }
}

but then add a generic implementation like this

impl<T> Mul<T> for MyFloat {
    type Output = MyFloat;
    fn mul(self, _other: T) -> MyFloat {
        self
    }
}

This is the reasoning behind why you can’t write a

impl<T: Float> Mul<Vector3d<T>> for T {
    type Output = Vector3d<T>;
    fn mul(self, rhs: Self::Output) -> Self::Output {
        Self::Output {x : rhs.x * self, y : rhs.y * self, z : rhs.z * self}
    }
}

(by the way, note that you don’t need to add those Sub, Add, Mul, and Div bounds to the Float bound since they are included in the Num supertrait of Float)

Rust solves the possible conflicts from such an implementation Mul<Vector3d<T>> for T, as demonstrated above, by requiring, well, as the error message will explain:

error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`Vector3d<T>`)
  --> src/lib.rs:13:6
   |
13 | impl<T: Float> Mul<Vector3d<T>> for T {
   |      ^ type parameter `T` must be covered by another type when it appears before the first local type (`Vector3d<T>`)
   |
   = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
   = note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

error: aborting due to previous error

or simply speaking: there is an arbitrary requirement that some plain variable T can only be the right operand of Mul but not the left. This way only one of the two conflicting implementations

impl<T: Float> Mul<Vector3d<T>> for T

and

impl<T> Mul<T> for MyFloat

are allowed.

So what can you do? Implement generic multiplication from the right:

impl<T: Float> Mul<T> for Vector3d<T> {
    type Output = Vector3d<T>;
    fn mul(self, rhs: T) -> Self::Output {
        Self::Output {x : self.x * rhs, y : self.y * rhs, z : self.z * rhs}
    }
}

and also add a manual list of implementations of multiplication from the left, so that at least f32 and f64 are supporting scalar * vector notation:

macro_rules! left_multiplication {
    ($($T:ty),*) => {
        $(
            impl Mul<Vector3d<$T>> for $T {
                type Output = Vector3d<$T>;
                fn mul(self, rhs: Self::Output) -> Self::Output {
                    Self::Output {x : rhs.x * self, y : rhs.y * self, z : rhs.z * self}
                }
            }
        )*
    }
}

left_multiplication!{
    f32, f64
}

Also note that this is basically the approach taken in e.g. the nalgebra crate, see right multiplication here, and left multiplication starting here.

5 Likes

Thanks for your answer. It is quite unfortunate but it is what it is..
However how I can combine multiple traits in single bound. I expect to have something like type alias that allow me to not repeat my self

That can be done by defining a new trait with supertraits and a generic impl.

trait HashAndEq: Hash + Eq {}
impl<T: ?Sized> HashAndEq for T where T: Hash + Eq {}

(playground)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.