Rust 错误 E0368:binary assignment operation += cannot be applied to type(不支持 +=)
E0368 的意思是:你对一个还不支持复合运算符的类型用了 +=、-= 或 *= 这样的运算符。它是 E0369 的 += 版“表亲”。你自己定义的结构体不会自动获得 +=,即使它已经支持 + 也不会。修法通常是一个简短的 impl AddAssign 块;如果是引用背后的数字,只需要加一个 *。这个系列的其他错误,请看 Rust 编译错误详解。
error[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money 不知道怎么做 += | ------^^^^^^^ | | | cannot use `+=` on type `Money` |note: an implementation of `AddAssign` might be missing for `Money` --> src/main.rs:1:1 |1 | struct Money { | ^^^^^^^^^^^^ must implement `AddAssign`第一行的意思是“二元赋值运算 += 不能用在 Money 类型上”。note 说 Money 可能缺少 AddAssign 的实现。
产生这个错误的程序是这样的:
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money 不知道怎么做 += println!("{}", wallet.cents);}第一行里的运算符会变,但错误是同一个。p -= step 要求 SubAssign,p *= 2 要求 MulAssign<{integer}>,意思是“乘以一个整数并赋值”。
这个错误是什么意思
Section titled “这个错误是什么意思”左边下面的标签说明了哪里出了问题:“cannot use += on type Money”(不能对 Money 类型使用 +=)。接着 note: 指向你的结构体,说它必须实现 AddAssign(must implement AddAssign)。这就是 += 背后的 trait。
想象一个存钱罐。往里投一枚硬币,这个动作很清楚。但如果有人递给你一个标着“Money”的盒子,说“把另一个盒子加到它上面”,你就得知道该怎么加:把分(cents)相加?还是把盒子摞起来?Rust 需要你把这条规则写下来,写一次就够。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”每个复合运算符其实都是一次伪装起来的方法调用。a += b 调用的是 AddAssign::add_assign(&mut a, b),a -= b 调用 SubAssign::sub_assign,以此类推。这些 trait 都在 std::ops 里。数字已经实现了它们。你新写的结构体一个都没有,而且数学运算符没有 #[derive] 可用。
最让人意外的是这一点:Add 和 AddAssign 是两个独立的 trait。即使 Point 已经实现了 Add,p += q 照样报 E0368。原因是它们做的事不一样。a + b 接收两个值,返回一个新值。a += b 通过 &mut self 原地修改 a,什么也不返回。原地修改可能便宜得多:String 的 += 会追加到已有的缓冲区,而不是新建一个字符串。Rust 让每个类型自己选择,所以它从不会悄悄地把 += 替你换成 +。
运算符 trait 在常用 trait和高级 trait里有讲解。
修复 1:实现 AddAssign(以及它的同伴们)
Section titled “修复 1:实现 AddAssign(以及它的同伴们)”当你的类型上的 += 有明确的含义(比如加钱或移动位置)时,选这个方法。你只需写一个修改 self 的方法。需要 SubAssign、MulAssign 等其他运算符时,也照同样的方式添加:
use std::ops::{AddAssign, SubAssign};
#[derive(Debug)]struct Money { cents: i64,}
impl AddAssign for Money { fn add_assign(&mut self, other: Money) { self.cents += other.cents; }}
impl SubAssign for Money { fn sub_assign(&mut self, other: Money) { self.cents -= other.cents; }}
fn main() { let mut wallet = Money { cents: 500 }; wallet += Money { cents: 150 }; // 收到小费 wallet -= Money { cents: 200 }; // 买了午饭 println!("{:?}", wallet);}输出:
Money { cents: 450 }注意 &mut self,而且没有返回类型。这就是它和 Add 的全部区别。在方法内部,self.cents += other.cents 能用,是因为 i64 已经实现了 AddAssign。
修复 2:已经有 Add 了?写 a = a + b,或者复用它
Section titled “修复 2:已经有 Add 了?写 a = a + b,或者复用它”当你的类型已经实现了 Add(也许是在修好 E0369 之后)时,选这个方法。如果这个类型是 Copy 的,直接写 pos = pos + step 就行,它只需要 Add。或者用一行调用你的 Add 来实现 AddAssign,这样 += 也能用了:
use std::ops::{Add, AddAssign};
#[derive(Debug, Clone, Copy)]struct Point { x: i32, y: i32,}
impl Add for Point { type Output = Point;
fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }}
impl AddAssign for Point { fn add_assign(&mut self, other: Point) { *self = *self + other; // 复用 Add }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // 只需要 Add pos += step; // 需要 AddAssign println!("{:?}", pos);}输出:
Point { x: 2, y: 4 }*self = *self + other 先把当前的值复制出来,相加,再把结果写回去。这只有在 Point 是 Copy 时才行得通。对于拥有堆数据的类型(比如带 Vec 字段的),请改用修复 1 那种原地修改的写法。
修复 3:字符串:s += &t 或 push_str
Section titled “修复 3:字符串:s += &t 或 push_str”当左边是字符串时,选这个方法。+= 可以用在拥有所有权的 String 上,而右边必须是 &str。有两种情况经常让人栽跟头:
cannot be applied to type `&str`:左边是一个字符串 slice,比如"Hello, "。&str是只读的视图,没法变长。先把它变成String。- 当
t也是String时,s += t报的不是 E0368,而是 E0308,“expected&str, foundString”(期望&str,却得到String),并附带提示s += &t。借用右边就好。
fn main() { let mut greeting = String::from("你好,"); // 拥有所有权,所以可以变长 let name = String::from("Ferris");
greeting += &name; // 借用右边 greeting.push_str("!"); // 同样的事,写成方法
let mut tag = "crab".to_string(); // 左边是 &str?把它变成 String tag += " #1";
println!("{greeting}"); println!("{tag}"); println!("{name} 仍然可以使用");}输出:
你好,Ferris!crab #1Ferris 仍然可以使用因为右边只是被借用,之后 name 仍然归你。+= 和 push_str 做的事完全一样,哪个读起来顺就用哪个。参见让 String 变长。
修复 4:在泛型代码里加上 T: AddAssign 约束
Section titled “修复 4:在泛型代码里加上 T: AddAssign 约束”当错误说 cannot be applied to type `T` 时,选这个方法。在泛型函数里,T 可以是任何类型,包括不支持 += 的类型。rustc 的 help 会准确告诉你要加什么:consider further restricting type parameter `T` with trait `AddAssign`(考虑用 trait AddAssign 进一步约束类型参数 T)。
use std::ops::AddAssign;
fn total<T>(items: &[T]) -> Twhere T: Copy + Default + AddAssign,{ let mut sum = T::default(); for &item in items { sum += item; } sum}
fn main() { println!("{}", total(&[1, 2, 3])); println!("{}", total(&[0.5, 0.25]));}输出:
60.75现在 total 适用于任何能复制、有初始值、支持 += 的类型:整数、浮点数,以及实现了这些 trait 之后的你自己的 Money。如果只是普通数字,items.iter().sum() 一行就能做同样的事。参见泛型函数。
用 HashMap 计数。 先写 let count = counts.entry(word).or_insert(0);,再写 count += 1;,就会报 cannot be applied to type `&mut i32`。count 是一个引用,不是数字本身。rustc 的 help 是:“+= can be used on i32 if you dereference the left-hand side”(如果对左边解引用,就可以对 i32 使用 +=)。修法:*count += 1;。参见更新值。
把结构体乘以一个数。 p *= 2 需要 MulAssign<{integer}>,因为右边是一个数字,而不是另一个 Point。修法:为这个右侧类型实现 trait:
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}在循环里累加。 total += item.price,而 price 是你自己的 Money 类型。修复 1 就能让它工作。如果你更想写 prices.iter().copied().sum(),那需要的是 Sum trait,没有它你会得到 E0277(“a value of type Money cannot be made by summing an iterator”,无法通过对迭代器求和得到 Money 类型的值)。
- E0369:binary operation cannot be applied:同样的问题,只不过针对
+、==、<等不带赋值的运算符。 - E0308:mismatched types:两个
String写s += t时得到的错误(借用一下就好:s += &t)。 - E0277:trait bound not satisfied:左边的类型支持
+=,但不支持这个右侧类型,比如total是整数时写total += 2.5,会得到cannot add-assign `{float}` to `{integer}`。 - E0384:cannot assign twice:对一个没有用
mut声明的变量写x += 1。运算符没问题,只是这个变量不可变。 - 所有编译错误:全部错误页面的列表。
小测验
1. Point 实现了 Add,但 p += q 仍然报 E0368。为什么?
2. count 来自 map.entry(word).or_insert(0)。哪一行能编译?
3. s 和 t 都是 String。怎么把 t 追加到 s 后面?
得分: 0 / 3
- E0368 — Rust 错误代码索引(英文):这个错误的官方说明。
std::ops::AddAssign— 标准库文档(英文):+=背后的 trait,附示例;SubAssign、MulAssign等其他 trait 都列在std::ops(英文)里。- 高级 trait — Rust 程序设计语言:用
std::ops重载运算符,以及Rhs这样的默认类型参数。 - 使用字符串储存 UTF-8 编码的文本 — Rust 程序设计语言:为什么字符串用
push_str变长,以及为什么+右边要的是&str。 - 运算符重载 — 通过例子学 Rust:为自己的类型实现运算符 trait 的简短可运行示例。