Why isn't i32 : AddAssign<&i32>?

For some things I'm playing around with I wanted a little (math) vector, and decided to write it generically as a good exercise.

I wrote up the addition operators, taking ownership of everything, and it all went great.

#[derive(Debug)]
struct Vec2<T>(T, T);

use std::ops::Add;
impl<T> Add for Vec2<T> where T : Add {
    type Output = Vec2<T::Output>;
    fn add(self, rhs: Vec2<T>) -> Self::Output {
        Vec2(self.0 + rhs.0, self.1 + rhs.1)
    }
}

use std::ops::AddAssign;
impl<T> AddAssign for Vec2<T> where T : AddAssign {
    fn add_assign(&mut self, rhs: Vec2<T>) {
        self.0 += rhs.0;
        self.1 += rhs.1;
    }
}

fn main() {
    let mut a = Vec2(1, 2);
    let b = Vec2(3, 4);
    let c = Vec2(5, 6);
    a += b + c;

    println!("{:?}", a);
}

But then I tried to consume a value twice, which of course didn't work.

Ok, no problem, I'll just implement versions on borrows. After a few tries I get all the &'as in the right place, or at least the compiler is happy

impl<'a, T> Add for &'a Vec2<T> where &'a T : Add {
    type Output = Vec2< <&'a T as Add>::Output >;
    fn add(self, rhs: &'a Vec2<T>) -> Self::Output {
        Vec2(&self.0 + &rhs.0, &self.1 + &rhs.1)
    }
}

impl<'a, T> AddAssign<&'a Vec2<T>> for Vec2<T> where T : AddAssign<&'a T> {
    fn add_assign(&mut self, rhs: &'a Vec2<T>) {
        self.0 += &rhs.0;
        self.1 += &rhs.1;
    }
}

And my Add is working fine with borrows:

fn main() {
    let mut a = Vec2(1, 2);
    let b = Vec2(3, 4);
    let c = &a + &b;
    a += &b + &c;

    println!("{:?}", a);
}

But when I try to use AddAssign with borrows,

fn main() {
    let mut a = Vec2(1, 2);
    let b = Vec2(3, 4);
    let c = &a + &b;
    a += &c;

    println!("{:?} {:?}", a, c);
}

I get expected struct `Vec2`, found &Vec2<_>?! (Or, if I remove the move impl, cannot use `+=` on type `Vec2<{integer}>` .)

Checking the docs more closely, I see all of

impl Add<i32> for i32
impl<'a> Add<i32> for &'a i32
impl<'a> Add<&'a i32> for i32
impl<'a, 'b> Add<&'a i32> for &'b i32

but only

impl AddAssign<i32> for i32

Did I write my impl AddAssign incorrectly? Is there something about Rust that would prevent having the following?

impl<'a> AddAssign<&'a i32> for i32

(Obviously if I'm only using i32 I could just T:Copy it, but that feels wrong, even though I won't actually be using Vec2<BigInteger>.)


Edit: Weird, the details collapsers around some of the code worked in the preview, but not when saved.

2 Likes

If it fits your use case, you could add a T : Copy bound, and then add the dereferenced components.

Edit:
You could also use the Add impl instead of AddAssign by changing the bound to T : Add<&'a T> and the code to:
self.0 = self.0 + rhs.0;
self.1 = self.1 + rhs.1;

There's a bunch of ways I could workaround it. The T:Copy is a reasonable one, or including typeing .clone()ing in my I-know-it's-i32 code, or ...

But mostly I'm curious if I should open a bug/pr to add AddAssign impls to the fundamental Copy types, or if there's some reason that's not possible or a bad idea.


Also, I tried the impl-AddAssign-with-Add idea, and I think it boils down to the same as the T:Copy solution, since it gives "cannot move out of borrowed content" errors on self.0 and self.1:

impl<'a, T> AddAssign<&'a Vec2<T>> for Vec2<T> where T : Add<&'a T, Output=T> {
    fn add_assign(&mut self, rhs: &'a Vec2<T>) {
        self.0 = self.0 + &rhs.0;
        self.1 = self.1 + &rhs.1;
    }
}

(I also needed to make it T : Add<&'a T, Output=T>. Without the Output=T I got a very confusing "expected type parameter, found associated type" error.)

1 Like