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.

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.