Ошибка 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 просит один раз записать это правило.
Почему Rust так делает
Заголовок раздела «Почему 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.
Исправление 3: строки — s += &t или push_str
Заголовок раздела «Исправление 3: строки — s += &t или push_str»Выбирай это, когда слева строка. += работает с владеющей String, а справа должен быть &str. Здесь люди спотыкаются о два случая:
cannot be applied to type `&str`: слева строковый срез вроде"Hello, ".&str— это вид только для чтения, расти он не может. Сначала сделай из негоString.s += t, гдеtтожеString, — это не E0368. Это E0308, «expected&str, foundString», с подсказкой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!краб #1Ferris всё ещё можно использоватьПоскольку правая часть только заимствуется, 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]) -> 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 работает для любого типа, который можно копировать, у которого есть начальное значение и который поддерживает +=: для целых чисел, чисел с плавающей точкой и для твоего 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
Что почитать
Заголовок раздела «Что почитать»- E0368 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки.
std::ops::AddAssign— документация стандартной библиотеки (на английском): трейт, который стоит за+=, с примером;SubAssign,MulAssignи остальные перечислены вstd::ops(на английском).- Продвинутые типажи — книга «Язык программирования Rust» (перевод на русский): перегрузка операторов через
std::opsи параметры типа по умолчанию вродеRhs. - Хранение текста в кодировке UTF-8 в строках — книга «Язык программирования Rust» (перевод на русский): почему строки растут через
push_str, а+берёт&strсправа. - Перегрузка операторов — Rust на примерах (перевод на русский): короткий запускаемый пример реализации трейтов операторов для своих типов.