How to implement AddAssign for &mut ref?

I've lost in generics.

I have a simple struct:

#[derive(Debug, Clone, Copy)]
struct Foo<T>{
    value: T
}

I implemented += it for Foo<T>:

impl<T: Copy + std::ops::AddAssign, R: Into<Foo<T>>> std::ops::AddAssign<R>
    for Foo<T>
{
    fn add_assign(&mut self, rhs: R)
    {
        let rhs = rhs.into();
        self.value+=rhs.value
    }
}

But now I want to do it for (&mut Foo) += Foo

My naive approach:

impl<T: Copy + std::ops::AddAssign, R: Into<Foo<T>>> std::ops::AddAssign<R>
    for &mut Foo<T>

actually broke compiler: panicked at 'Box<dyn Any>', compiler/rustc_errors/src/lib.rs:1425:13 · Issue #101376 · rust-lang/rust · GitHub

but I still do not understand how to express that I want to implement trait for reference to my value.

It's unfortunate you ran into an ICE, but it doesn't ultimately matter. (&mut a_foo) += ... isn't valid because the lhs of an assignment operator has to be a place expression. You can see the proper error in this reduced example:

error[E0067]: invalid left-hand side of assignment
  --> src/lib.rs:15:17
   |
15 |     (&mut left) += right;
   |     ----------- ^^
   |     |
   |     cannot assign to this expression

Why do you want to do that? left += right should work fine.

1 Like

Because I may have only mut reference to the structue and I still want to do +=.

F.e.

fn bar(mut f: &mut Foo<i32>){
    f += Foo{value:1};
}

Oh... Actually, I can. With my impl<...> for &Foo<T> it compiled.

Why can I do this:

fn bar(mut f: &mut Foo<i32>){
    f += Foo{value:1};
}

fn main(){
    let mut left = Foo{value:1};
    let right = Foo{value:1};
    bar(&mut left);
    println!("Left: {:?}", left);

but I can't do this:

(&mut left) += &right;

?

Outside of ICE, I see error message: cannot assign to this expression, and I can't understand, why can I do this in function with mutable reference as argument, but can't do it with mutable reference directly.

Actually, forget the function.

fn main(){
    let mut left = Foo{value:1};
    let right = Foo{value:1};
    bar(&mut left);
    println!("Left: {:?}", left);
    let mut left_ref: &mut Foo<i32> = &mut left;
    left_ref += &right;
    println!("Left: {:?}", left_ref);
    // (&mut left) += &right;
}

Why I can do everything uncommented, but can't do commented?

It's a reference, dereference it first.

*left_ref += right;

Implementing AddAssign for a reference, but not mutating the reference, seems... needlessly confusing.

Because the left-hand side of an assignment has to be a place expression according to the rules of the language. In fact, place expressions and value expressions used to be called lvalues and rvalues precisely because of their placement with relation to the = (a convention probably borrowed from C).

A local variable (that happens to contain a &mut reference) is a place expression, so it works. But since the local variable itself isn't being mutated by += (but only the value behind it), I would consider that AddAssign implementation misleading.

3 Likes

If you had a T and you wanted a &mut T in a place expression without an extra assignment, that would be *(&mut &mut T).

(I'm not saying this is a good idea; just go with the idiomatic way and implement AddAssign for T and use * when you have a &mut T value.)

I guess this is what the reference is talking about when it says

Unlike other place operands, the assigned place operand must be a place expression. Attempting to use a value expression is a compiler error rather than promoting it to a temporary.

3 Likes