Implementing Operator overloading with Generics

Hi all,
I am trying to impement a Vec3 type which will behave nicely when multiplying with float types. Using the num crate I have access to the Float bound trait. and have it working in some instances but not all. Sample snippets below.

// this compiles fine
impl <F: Float> ops::Mul<F> for Vec3 
{
    type Output = Vec3;
    fn mul(self, _rhs: F) -> Vec3 {
        let val =_rhs.to_f64().unwrap();
        Vec3(self.0 * val, self.2 * val, self.2 * val)
    }
}

// This is the implementaiton that complains
impl <F: Float>  ops::Mul<&Vec3> for F{
    type Output = Vec3;
    fn mul(self, rhs: &Vec3)->Self::Output{
        let val = self.to_f64().unwrap();
        Vec3(val * rhs.0, val * rhs.1, val * rhs.2)
    }
}

// These are the working implemtnations I am trying to replace with the one above
// impl ops::Mul<&Vec3> for f64 {
//     type Output = Vec3;
//     fn mul(self, rhs: &Vec3) -> Self::Output {
//         Vec3(self * rhs.0, self * rhs.1, self * rhs.2)
//     }
// }


// impl ops::Mul<&Vec3> for f32 {
//     type Output = Vec3;
//     fn mul(self, rhs: &Vec3) -> Self::Output {
//         let val = self as f64;
//         Vec3(val * rhs.0, val * rhs.1, val * rhs.2)
//     }
// }

The error listed is
error[E0210]: type parameter F must be covered by another type when it appears before the first local type (Vec3)
--> src/lib.rs:109:7
|
109 | impl <F: Float> ops::Mul<&Vec3> for F{
| ^ type parameter F must be covered by another type when it appears before the first local type (Vec3)
|
= 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

Not clear how to resolve this, not clear what they mean that Type F must be 'covered' by another type...
Any suggestions?

You can macro up an implementation for concrete types.

impl ops::Mul<&Vec3> for f32 {
    type Output = Vec3;
    fn mul(self, rhs: &Vec3)->Self::Output{
        ...
    }
}

Or make a newtype that wraps floats, but probably you'd find that too unergonomic.

They mean that because Mul is a foreign trait, the implementing type can't be just a generic. It can be

  • A concrete type (even foreign, if you have a local type elsewhere, and you do[1])
  • A specific generic type like Vec<F>
    • Vec is "covering" the generic F

There's more details in this RFC.


  1. Vec3 is local and &Locals are also considered local for the purposes of this check -- the "orphan check" ↩ī¸Ž

2 Likes

Thanks, that is helpful.

So the Macro option is basically a case of a macro that will expand to produce the concrete examples which I have already implemented (in comments). Which seems reasonable. Haven't seen how to implement macros yet (going through the Rust book) , but that should be easily researchable.

Yeah wrapping the floats sounds like additional complexity without much return. But from a learning point of view is this a common Rust pattern?

I deliberately chose to use my Vec3 to be a concrete f64, as it seemed like that would make things more predictable, but from the sound of it implementing it as a generic with the Float bound would potentially get around that :-).

I think it depends on the use case. Newtypes are a common concept generally, but newtyped primitives can be annoying because all your literals end up looking like Wrapper(0.0) or such.

Follow up thought: You could do both the macro and the newtype if that makes sense for your use case.