Implementing Add for struct with fixed-sized array


#1

I need help navigating some confusing errors. Here’s the code (playground link):

use std::ops::Add;

#[derive(Clone, Copy)]
pub struct Foo<T> {
  pub m: [T; 1]
}

impl<T> Add<Foo<T>> for Foo<T> where T: Add<T> + Copy {
    type Output = Foo<T>;

    fn add(self, rhs: Self) -> Self::Output {
        Foo { m: [Add::<T>::add(self.m[0], rhs.m[0])] }
    }
}

fn main() {
}

I get the following errors

<anon>:5:3: 5:16 error: the trait `core::marker::Copy` is not implemented for the type `T` [E0277]
<anon>:5   pub m: [T; 1]
           ^~~~~~~~~~~~~
<anon>:3:10: 3:15 note: in this expansion of #[derive_Clone] (defined in <anon>)
<anon>:5:3: 5:16 help: see the detailed explanation for E0277
<anon>:5:3: 5:16 note: required by `core::clone::Clone::clone`
<anon>:12:9: 12:56 error: mismatched types:
 expected `Foo<T>`,
    found `Foo<<T as core::ops::Add>::Output>`
(expected type parameter,
    found associated type) [E0308]
<anon>:12         Foo { m: [Add::<T>::add(self.m[0], rhs.m[0])] }
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:12:9: 12:56 help: see the detailed explanation for E0308
error: aborting due to 2 previous errors
playpen: application terminated with error code 101

The first error can be handled by adding where T: Copy on the struct definition itself and seems to be caused by the presence of m: [T; 1]. If I write it out as individual elements (eg. m: T in this case with only one element), then that error disappears and I don’t need the T: Copy constraint. Why is that there?

The second error still persists, though. I tried to resolve it a few ways including, implementing Add<&Foo<T>> on &Foo<T>, etc, with struct Foo<T> { pub m: T } and removing all copy constraints to simplify matter, but no dice. It’s especially confusing because Foo<<T as core::ops::Add>::Output> is exactly Foo<T> give that the impl has T: Add<T> as a constraint.

Any insights? Could the error be improved?


#2

Ok, I figured out the answer to the second one. I was, of course, thinking in terms of T = f64 and T = f32 both of which have the property that <T as Add>::Output = T so I treated them interchangeably. But the generic code has no such knowledge, so I should have been using <T as Add>::Output to define the result of the addition.
Also, since I am using Add::add(self.m[0], rhs.m[0]) in a staticly-sized array (which seems to require Copy), I need to add that constraint to the impl as well.

Here’s the compiling solution

use std::ops::Add;

#[derive(Clone, Copy)]
pub struct Foo<T> where T: Copy {
  pub m: [T; 1]
}

impl<T> Add<Foo<T>> for Foo<T> where T: Add<T> + Copy, <T as Add>::Output: Copy {
    type Output = Foo<<T as Add>::Output>;

    fn add(self, rhs: Self) -> Self::Output {
        Foo { m: [Add::<T>::add(self.m[0], rhs.m[0])] }
    }
}

Still not clear why a staticly-sized array requires Copy from its element type. I don’t see it as any different from having individual fields, but packed together so that one can use array indexing. I would expect the only time Copy to be needed to be when using the [value; 3] syntax (eg. for initializing an array of type [T; 3]).