Adding two types in any combination: T is not a type parameter for some local type — or is it?

I'm implementing a 3D vector struct Vec3, accompanied by a NormedVec3 struct for representing normalized vectors, plus a Vector3 trait that both structs implement. I want to be able to add these vectors together in any combination or order of types and get back a Vec3, so to save on typing, I tried this:

impl<T: Vector3, V: Vector3> Add<V> for T {
    type Output = Vec3;
    
    fn add(self, rhs: V) -> Vec3 {
        ...
    }
}

[Playground link]

The error I get from this is:

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
  --> src/lib.rs:73:6
   |
73 | impl<T: Vector3, V: Vector3> Add<V> for T {
   |      ^ type parameter `T` must be used as the type parameter for some local type
   |
   = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
   = note: only traits defined in the current crate can be implemented for a type parameter

But ... T is a type parameter for some local type (Vector3), isn't it? Or is the problem that Vector3 is a trait rather than a concrete type? How can I write the necessary Add implementations with a minimum of typing?

No, T doesn't parameterize anything here. It's the type for which you are attempting to implement the trait. (Vector3 doesn't have any type parameters anyway.)

If this code were allowed, because both T and V are type variables, foreign crates would also be allowed to add an implementation of the exact same shape, causing a conflict.

2 Likes

They're trying to convey that the type parameter T is "uncovered" here:

impl<T: Vector3, V: Vector3> Add<V> for T
//                                      ^

Where as it would be covered here:

struct MyWrapper<T>(T);
impl<T: Vector3, V: Vector3> Add<V> for MyWrapper<T>
// `MyWrapper` covers `T`

The rules are here and that RFC also has a section explaining why they exist more generally.

OK, so, knowing that, I assume the best way to get what I want it is to write two Add implementations, one for Vector3 + Vec3 = Vec3, the other for Vector3 + NormedVec3 = Vec3. Unfortunately, trying to do the first one with impl<T: Vector3> Add<Vec3> for T { type Output = Vec3; ... } hit me with error E0210. I'm guessing that I can only implement Vector3 + Vector3 = Vec3 (where the two Vector3's are the same type) via a blanket implementation, after which I'll need to do NormedVec3 + Vec3 = Vec3 and Vec3 + NormedVec3 = Vec3 as two more trait implementations. Does that work? Is there a better/shorter way?

And now I'm finding that impl<T> Add for T where T: Vector3 doesn't work either. I thought that was how you did blanket implementations? Or would that only be an option here if Add belonged to my crate?

You can't ever have

impl<TypeParameter: AnyBoundsAtAll> ForeignTrait<...> for TypeParameter

So instead of

impl<T: ...> Add<LocalType> for T

Use

impl<T: ...> Add<T> for LocalType

For example.

3 Likes

Yes. You have to consider that all crates are equal, so if you are allowed to write an impl of a particular shape, then everyone else is. Therefore, you always have to consider what would happen if someone else would write the same kind of impl – and if that would cause a conflict, it has to be disallowed.