Перейти к содержимому

Ошибка 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 doesn't know how to do +=
| ------^^^^^^^
| |
| 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, и, похоже, для Money не хватает реализации AddAssign. Вот программа, которая выдаёт эту ошибку:

struct Money {
cents: i64,
}
fn main() {
let mut wallet = Money { cents: 500 };
let tip = Money { cents: 150 };
wallet += tip; // ❌ Money doesn't know how to do +=
println!("{}", wallet.cents);
}

Комментарий говорит: Money не знает, как выполнять +=. Оператор в первой строке может быть другим, но ошибка та же. p -= step требует SubAssign, а p *= 2 требует MulAssign<{integer}>, то есть «умножить с присваиванием на целое число».

Подпись под левой частью говорит, что не так: «cannot use += on type Money» (нельзя использовать += с типом Money). Затем note: указывает на твою структуру и говорит, что она должна реализовать AddAssign. Это трейт, который стоит за +=.

Представь копилку. Бросить в неё монету — понятное действие. Но если тебе дают коробку с надписью «Деньги» и говорят «добавь к ней вот эту коробку», нужно знать как: сложить копейки? поставить коробки друг на друга? Rust просит один раз записать это правило.

Каждый составной оператор — это замаскированный вызов метода. a += b вызывает AddAssign::add_assign(&mut a, b), a -= b вызывает SubAssign::sub_assign и так далее. Все эти трейты лежат в std::ops. Числа уже их реализуют. А у твоей новой структуры поначалу нет ни одного, и #[derive] для математических операторов не существует.

Вот что удивляет многих: Add и AddAssign — разные трейты. Даже если Point уже реализует Add, p += q всё равно даёт E0368. Причина в том, что они делают разную работу. a + b берёт два значения и возвращает новое. a += b меняет a на месте через &mut self и ничего не возвращает. Изменение на месте может быть гораздо дешевле: += у String дописывает текст в существующий буфер, а не строит новую строку. Rust позволяет каждому типу выбрать самому, поэтому никогда не превращает += в + за тебя молча.

Трейты операторов разобраны в уроках Распространённые трейты и Продвинутые трейты.

Исправление 1: реализуй AddAssign (и родственные трейты)

Заголовок раздела «Исправление 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 или переиспользуй его

Заголовок раздела «Исправление 2: уже есть Add? Пиши a = a + b или переиспользуй его»

Выбирай это, когда твой тип уже реализует Add (может быть, после исправления E0369). Если тип Copy, можно просто написать pos = pos + step — для этого нужен только Add. Или реализуй AddAssign в одну строку через твой Add, чтобы работал и +=:

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.

Выбирай это, когда слева строка. += работает с владеющей String, а справа должен быть &str. Здесь люди спотыкаются о два случая:

  • cannot be applied to type `&str`: слева строковый срез вроде "Hello, ". &str — это вид только для чтения, расти он не может. Сначала сделай из него String.
  • s += t, где t тоже String, — это не E0368. Это E0308, «expected &str, found String», с подсказкой s += &t. Просто заимствуй правую часть.
fn main() {
let mut greeting = String::from("Привет, "); // владеющая строка, поэтому может расти
let name = String::from("Ferris");
greeting += &name; // заимствуем правую часть
greeting.push_str("!"); // то же самое, но методом
let mut tag = "краб".to_string(); // слева &str? сделай из него String
tag += " #1";
println!("{greeting}");
println!("{tag}");
println!("{name} всё ещё можно использовать");
}

Вывод:

Привет, Ferris!
краб #1
Ferris всё ещё можно использовать
▶ Попробуй сам

Поскольку правая часть только заимствуется, name после этого по-прежнему твой. += и push_str делают ровно одно и то же; выбирай то, что читается лучше. Подробнее в уроке Строки.

Исправление 4: добавь ограничение T: AddAssign в обобщённом коде

Заголовок раздела «Исправление 4: добавь ограничение T: AddAssign в обобщённом коде»

Выбирай это, когда ошибка говорит cannot be applied to type `T`. Внутри обобщённой функции T может быть любым типом, даже без +=. Подсказка rustc точно говорит, что добавить: consider further restricting type parameter `T` with trait `AddAssign` (ограничь параметр типа T трейтом AddAssign).

use std::ops::AddAssign;
fn total<T>(items: &[T]) -> T
where
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]));
}

Вывод:

6
0.75
▶ Попробуй сам

Теперь total работает для любого типа, который можно копировать, у которого есть начальное значение и который поддерживает +=: для целых чисел, чисел с плавающей точкой и для твоего Money, как только он реализует эти трейты. Для обычных чисел то же самое в одну строку делает items.iter().sum(). Подробнее в уроке Обобщения.

Подсчёт с помощью HashMap. let count = counts.entry(word).or_insert(0);, а затем count += 1; даёт cannot be applied to type `&mut i32`. count — это ссылка, а не число. Подсказка rustc: «+= can be used on i32 if you dereference the left-hand side» (+= можно применить к i32, если разыменовать левую часть). Исправление: *count += 1;. Подробнее в уроке HashMap.

Умножение структуры на число. p *= 2 требует MulAssign<{integer}>, потому что справа число, а не другой Point. Исправление: реализуй трейт для этого типа правой части:

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, а без него получишь E0277 («a value of type Money cannot be made by summing an iterator»).

  • E0369: binary operation cannot be applied: та же проблема для +, ==, < и других операторов без присваивания.
  • E0308: mismatched types: то, что ты получишь для s += t с двумя String (заимствуй: s += &t).
  • E0277: trait bound not satisfied: += для левого типа есть, но не с таким правым типом, например cannot add-assign `{float}` to `{integer}` для total += 2.5, когда total — целое число.
  • E0384: cannot assign twice: x += 1 для переменной, объявленной без mut. С оператором всё в порядке, просто переменная не изменяемая.
  • Все ошибки компилятора: полный список страниц с ошибками.

Проверь себя

1. Point реализует Add, но p += q всё равно даёт E0368. Почему?

2. count получен из map.entry(word).or_insert(0). Какая строка скомпилируется?

3. s и t — обе String. Как дописать t к s?

Результат: 0 / 3