Zum Inhalt springen

Rust-Fehler E0368: binary assignment operation += cannot be applied to type

E0368 bedeutet: Du hast einen zusammengesetzten Operator wie +=, -= oder *= auf einen Typ angewendet, der ihn noch nicht unterstützt. Er ist der +=-Cousin von E0369. Deine eigenen Structs bekommen += nicht automatisch, nicht einmal, wenn sie + schon können. Die Lösung ist meist ein kurzer impl AddAssign-Block, und bei Zahlen hinter einer Referenz ist es ein einziges *. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.

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`

Dieses Programm erzeugt den Fehler:

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);
}

Der Kommentar sagt: Money weiß nicht, wie += geht. Der Operator in der ersten Zeile der Meldung ändert sich, aber es ist derselbe Fehler. p -= step verlangt SubAssign, und p *= 2 verlangt MulAssign<{integer}>, also „mit einer ganzen Zahl multiplizieren und zuweisen“.

Die Beschriftung unter der linken Seite sagt, was schiefgelaufen ist: „cannot use += on type Money“ (+= kann auf den Typ Money nicht angewendet werden). Die note: zeigt dann auf dein Struct und sagt, es muss AddAssign implementieren. Das ist der Trait (eine Art Fähigkeit) hinter +=.

Stell dir ein Sparschwein vor. Eine Münze einwerfen ist eine klare Sache. Aber wenn dir jemand eine Kiste mit der Aufschrift „Geld“ gibt und sagt: „Füg diese andere Kiste hinzu“, musst du wissen, wie: die Cents zusammenzählen? die Kisten stapeln? Rust will, dass du diese Regel einmal aufschreibst.

Jeder zusammengesetzte Operator ist ein versteckter Methodenaufruf. a += b ruft AddAssign::add_assign(&mut a, b) auf, a -= b ruft SubAssign::sub_assign auf, und so weiter. Alle diese Traits stehen in std::ops. Zahlen implementieren sie schon. Dein neues Struct startet ohne einen einzigen davon, und für Rechenoperatoren gibt es kein #[derive].

Jetzt kommt der Teil, der viele überrascht: Add und AddAssign sind getrennte Traits. Selbst wenn Point schon Add implementiert, ergibt p += q trotzdem E0368. Der Grund: Die beiden erledigen verschiedene Aufgaben. a + b nimmt zwei Werte und gibt einen neuen zurück. a += b ändert a direkt an Ort und Stelle über &mut self und gibt nichts zurück. Das direkte Ändern kann viel günstiger sein: += bei einem String hängt an den vorhandenen Speicher an, statt einen neuen String zu bauen. Rust lässt jeden Typ selbst wählen und macht deshalb nie heimlich aus += ein +.

Operator-Traits werden unter Wichtige Traits und Fortgeschrittene Traits erklärt.

Lösung 1: AddAssign (und seine Geschwister) implementieren

Abschnitt betitelt „Lösung 1: AddAssign (und seine Geschwister) implementieren“

Nimm das, wenn += für deinen Typ eine klare Bedeutung hat, etwa Geld hinzufügen oder eine Position verschieben. Du schreibst eine Methode, die self ändert. SubAssign, MulAssign und die anderen fügst du genauso hinzu, wenn du sie brauchst:

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 }; // Trinkgeld bekommen
wallet -= Money { cents: 200 }; // Mittagessen gekauft
println!("{:?}", wallet);
}

Ausgabe:

Money { cents: 450 }
▶ Selbst ausprobieren

Beachte &mut self und den fehlenden Rückgabetyp. Das ist der ganze Unterschied zu Add. Innen funktioniert self.cents += other.cents, weil i64 schon AddAssign hat.

Lösung 2: Du hast schon Add? Schreib a = a + b oder nutz es wieder

Abschnitt betitelt „Lösung 2: Du hast schon Add? Schreib a = a + b oder nutz es wieder“

Nimm das, wenn dein Typ schon Add implementiert (vielleicht nachdem du E0369 behoben hast). Ist der Typ Copy, kannst du einfach pos = pos + step schreiben. Das braucht nur Add. Oder du implementierst AddAssign in einer Zeile, indem du dein Add aufrufst. Dann funktioniert auch +=:

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 wiederverwenden
}
}
fn main() {
let mut pos = Point { x: 0, y: 0 };
let step = Point { x: 1, y: 2 };
pos = pos + step; // braucht nur Add
pos += step; // braucht AddAssign
println!("{:?}", pos);
}

Ausgabe:

Point { x: 2, y: 4 }
▶ Selbst ausprobieren

*self = *self + other kopiert den aktuellen Wert heraus, addiert und schreibt das Ergebnis zurück. Das klappt nur, weil Point Copy ist. Für einen Typ, der Daten auf dem Heap besitzt (etwa ein Vec-Feld), schreibst du stattdessen die direkte Version aus Lösung 1.

Nimm das, wenn links ein String steht. += funktioniert bei einem eigenen String, und rechts muss ein &str stehen. Zwei Fälle bringen Leute oft durcheinander:

  • cannot be applied to type `&str`: Links steht ein String-Slice wie "Hello, ". Ein &str ist eine reine Leseansicht, er kann nicht wachsen. Mach zuerst einen String daraus.
  • s += t, wobei t auch ein String ist, ist nicht E0368. Es ist E0308, „expected &str, found String“, mit der Hilfe s += &t. Leih dir einfach die rechte Seite aus.
fn main() {
let mut greeting = String::from("Hallo, "); // eigener String, kann also wachsen
let name = String::from("Ferris");
greeting += &name; // rechte Seite ausleihen
greeting.push_str("!"); // dasselbe als Methode
let mut tag = "Krabbe".to_string(); // &str links? Mach einen String daraus
tag += " #1";
println!("{greeting}");
println!("{tag}");
println!("{name} ist noch benutzbar");
}

Ausgabe:

Hallo, Ferris!
Krabbe #1
Ferris ist noch benutzbar
▶ Selbst ausprobieren

Weil die rechte Seite nur ausgeliehen ist, gehört dir name danach immer noch. += und push_str machen genau dasselbe; nimm, was sich besser liest. Siehe Strings.

Lösung 4: Eine Bedingung T: AddAssign in generischem Code

Abschnitt betitelt „Lösung 4: Eine Bedingung T: AddAssign in generischem Code“

Nimm das, wenn der Fehler cannot be applied to type `T` lautet. In einer generischen Funktion könnte T jeder Typ sein, auch einer ohne +=. Die Hilfe von rustc sagt genau, was du hinzufügen musst: consider further restricting type parameter `T` with trait `AddAssign` (schränke den Typparameter T zusätzlich mit dem Trait AddAssign ein).

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]));
}

Ausgabe:

6
0.75
▶ Selbst ausprobieren

Jetzt funktioniert total für jeden Typ, der sich kopieren lässt, einen Startwert hat und += unterstützt: Ganzzahlen, Gleitkommazahlen und auch dein eigenes Money, sobald es diese Traits implementiert. Für einfache Zahlen erledigt items.iter().sum() dasselbe in einer Zeile. Siehe Generics.

Zählen mit einer HashMap. let count = counts.entry(word).or_insert(0); und danach count += 1; ergibt cannot be applied to type `&mut i32`. count ist eine Referenz, nicht die Zahl selbst. Die Hilfe von rustc: „+= can be used on i32 if you dereference the left-hand side“ (+= geht bei i32, wenn du die linke Seite dereferenzierst). Lösung: *count += 1;. Siehe HashMap.

Ein Struct mit einer Zahl skalieren. p *= 2 braucht MulAssign<{integer}>, denn rechts steht eine Zahl, kein weiterer Point. Lösung: Implementiere den Trait für genau diesen Typ auf der rechten Seite:

impl std::ops::MulAssign<i32> for Point {
fn mul_assign(&mut self, factor: i32) {
self.x *= factor;
self.y *= factor;
}
}

Laufende Summen in einer Schleife. total += item.price, wobei price dein eigener Typ Money ist. Mit Lösung 1 funktioniert das. Möchtest du lieber prices.iter().copied().sum() schreiben, brauchst du stattdessen den Trait Sum. Ohne ihn bekommst du E0277 („a value of type Money cannot be made by summing an iterator“).

Kurzer Check

1. Point implementiert Add, aber p += q ergibt trotzdem E0368. Warum?

2. count kommt von map.entry(word).or_insert(0). Welche Zeile kompiliert?

3. s und t sind beide String. Wie hängst du t an s an?

Punkte: 0 / 3