AddAssign 和 '+=' 运算符之间的关系

Relation between AddAssign and '+=' Operator

到目前为止,我的理解是,在 Rust 中,运算符基本上是特征方法调用的语法糖。特别是,我认为 a += b 等同于写作 a.add_assign(b)。今天我很惊讶地听到 rustc (1.44.1) 的以下内容:

error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut u8`
 --> src/main.rs:2:5
  |
2 |     a += b;
  |     -^^^^^
  |     |
  |     cannot use `+=` on type `&mut u8`
  |
help: `+=` can be used on 'u8', you can dereference `a`
  |
2 |     *a += b;
  |     ^^

导致错误消息的代码是 (Playground)

fn test_add_assign(a: &mut u8, b: u8) {
    a += b;
}

fn main() {
    let mut test = 1;
    test_add_assign(&mut test, 1);
    assert_eq!(test, 2);
}

现在,编译器是正确的,编写 *a += b 可以工作,并且还可以正确地将新变量分配给 a。然而,令我惊讶的是, a.add_assign(b) 也可以正常工作而无需取消引用 a (Playground):

fn test_add_assign(a: &mut u8, b: u8) {
    a.add_assign(b);
}

考虑到 AddAssign 的文档只是说明

The addition assignment operator +=.

我在想:AddAssign+=运算符是什么关系,如果不是调用trait方法的基本语法糖?

你或多或少是对的。

我认为让您感到困惑的问题是方法函数调用中的自动取消引用。它在 中有详细说明,但基本上它说您可以使用值或引用或对引用的引用来调用成员函数,并且它会起作用:

let x = 42;
let _ = x.to_string(); //ok
let _ = (&x).to_string(); //ok
let r = &x;
let _ = r.to_string(); //ok
let _ = (*r).to_string(); //ok

但是当使用运算符时,自动 deref 不适用。所以:

let mut x = 42;
x += 1; //ok;
x.add_assign(1); //ok
let r: &mut i32 = &mut x;
*r += 1; //ok
r += 1; //error: &mut i32 does not implement AddAssign
r.add_assign(1); //ok: r is auto-dereffed

请注意 += 的左侧表达式必须是要修改的值(右值),而不是对该值的引用。然后,实际上当你写 a += b 它相当于 AddAssign::add_assign(&mut a, b)

I thought that a += b was equivalent to writing a.add_assign(b).

不完全是,a += b实际上是翻译成::std::ops::AddAssign::add_assign(&mut a, b)。在您的示例中,这意味着您将传递 &mut &mut u8 作为第一个参数。

如果你仔细想想,这是有道理的。整数变量 i 的标准赋值写为 i = 3;。如果您想改为调用函数,则需要将 i 的可变引用传递给该函数,以便它实际上可以修改 i 的值。这同样适用于扩充作业。

请注意,方法调用语法 a.add_assign(b) 恰好适用于这种情况,因为 method calls treat the receiver in a special way。编译器通过隐式借用和取消引用接收器来查找匹配方法,直到找到匹配项。对带有类型参数的特征的方法调用又是特殊的,因为搜索甚至可能会继续为该方法找到 other 参数的匹配项(我认为没有记录此时在 Rust 参考中)。