Rust-Fehler E0369: binary operation cannot be applied to type (Operator fehlt)
E0369 bedeutet: Du hast einen Operator wie ==, < oder + auf einen Typ angewendet, der das noch nicht kann. In Rust sind Operatoren nicht automatisch in deine eigenen Structs und Enums eingebaut. Jeder Operator kommt von einem Trait (einer Art Fähigkeit), und dein Typ bekommt ihn erst, wenn du diesen Trait ableitest oder implementierst. Die Lösung ist meist eine einzige #[derive(...)]-Zeile, und der Compiler sagt dir sogar, welche. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“error[E0369]: binary operation `==` cannot be applied to type `Point` --> src/main.rs:9:10 |9 | if a == b { // ❌ Point doesn't know how to compare itself | - ^^ - Point | | | Point |note: an implementation of `PartialEq` might be missing for `Point` --> src/main.rs:1:1 |1 | struct Point { | ^^^^^^^^^^^^ must implement `PartialEq`help: consider annotating `Point` with `#[derive(PartialEq)]` |1 + #[derive(PartialEq)]2 | struct Point { |Dieses Programm erzeugt den Fehler:
struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; if a == b { // ❌ Point doesn't know how to compare itself println!("Same point!"); }}Der Kommentar sagt: Point weiß nicht, wie es sich vergleichen soll. Die erste Zeile der Meldung ändert sich je nach Operator, aber es ist derselbe Fehler:
binary operation `<` cannot be applied to type `Version`:PartialOrdfehlt (auch bei>,<=,>=).cannot add `Point` to `Point`:Addfehlt. Du siehst auch „cannot subtract“, „cannot multiply“ und so weiter.cannot add `&str` to `&str`: Du wolltest zwei String-Slices mit+verbinden.
Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Die Beschriftungen unter dem Operator zeigen den Typ auf jeder Seite, hier Point und Point. Dann zeigt die note: auf dein Struct und sagt, es muss PartialEq implementieren („must implement PartialEq“). Mit anderen Worten: „Du willst, dass ich zwei Points vergleiche, aber niemand hat mir gesagt, was gleich für einen Point bedeutet.“
Stell dir eine Schiedsrichterin vor, der man zwei Dinge in die Hand drückt und fragt: „Was ist größer?“ Bei Zahlen ist die Antwort klar. Bei zwei Rucksäcken braucht sie erst eine Regel: nach Gewicht? nach Volumen? Rust ist diese sorgfältige Schiedsrichterin.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“Jeder Operator ist ein versteckter Methodenaufruf. a == b ruft PartialEq::eq auf, a < b ruft PartialOrd::partial_cmp auf, und a + b ruft Add::add auf. Zahlen, String und viele Typen der Standardbibliothek implementieren diese Traits schon. Dein neues Struct startet ohne einen einzigen davon.
Rust rät nicht, denn Raten wäre manchmal falsch. Sind zwei User gleich, wenn ihre IDs übereinstimmen, oder nur, wenn jedes Feld übereinstimmt? Soll Point + Point die Koordinaten addieren, oder ist das Addieren von Punkten überhaupt sinnvoll? Du entscheidest, indem du den Trait ableitest (der Standard: Feld für Feld) oder dein eigenes impl schreibst. Die Traits werden auf der Seite Wichtige Traits erklärt.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: #[derive(PartialEq)] für == und !=
Abschnitt betitelt „Lösung 1: #[derive(PartialEq)] für == und !=“Nimm das für == und !=, wenn „gleich“ bedeutet „alle Felder sind gleich“. Das ist meistens die richtige Lösung, und genau das schlägt rustc vor. Wenn du Debug dazunimmst, kannst du das Struct auch ausgeben:
#[derive(Debug, PartialEq)]struct Point { x: i32, y: i32,}
fn main() { let a = Point { x: 1, y: 2 }; let b = Point { x: 1, y: 2 }; let c = Point { x: 5, y: 0 }; if a == b { println!("Derselbe Punkt!"); } println!("{:?} != {:?}: {}", a, c, a != c);}Ausgabe:
Derselbe Punkt!Point { x: 1, y: 2 } != Point { x: 5, y: 0 }: trueJedes Feld muss ebenfalls vergleichbar sein. Fehlt dem Typ eines Felds PartialEq, leite es auch für diesen Typ ab. Enums funktionieren genauso: #[derive(PartialEq)] auf enum Status sorgt dafür, dass status == Status::Banned kompiliert.
Lösung 2: #[derive(PartialOrd)] für < und >
Abschnitt betitelt „Lösung 2: #[derive(PartialOrd)] für < und >“Nimm das, wenn dein Typ eine natürliche Reihenfolge hat. PartialOrd braucht PartialEq, deshalb schlägt rustc #[derive(PartialEq, PartialOrd)] vor. Die abgeleitete Version vergleicht die Felder von oben nach unten: zuerst major, und nur wenn die gleich sind, minor. Die Reihenfolge der Felder in deinem Struct ist also wichtig:
#[derive(Debug, PartialEq, PartialOrd)]struct Version { major: u32, minor: u32,}
fn main() { let installed = Version { major: 1, minor: 4 }; let required = Version { major: 1, minor: 7 }; if installed < required { println!("Bitte aktualisiere von {:?}", installed); }
let next = Version { major: 2, minor: 0 }; println!("2.0 ist neuer als 1.7: {}", next > required);}Ausgabe:
Bitte aktualisiere von Version { major: 1, minor: 4 }2.0 ist neuer als 1.7: trueWillst du einen Vec deines Typs mit .sort() sortieren, brauchst du zusätzlich Eq und Ord. Siehe Sortieren.
Lösung 3: impl std::ops::Add für +
Abschnitt betitelt „Lösung 3: impl std::ops::Add für +“Nimm das, wenn das Addieren zweier Werte deines Typs eine klare Bedeutung hat, zum Beispiel Koordinaten oder Geldbeträge addieren. Für Rechenoperatoren gibt es kein derive, also schreibst du ein kurzes impl. Output ist der Typ, den + zurückgibt:
use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]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 } }}
fn main() { let start = Point { x: 1, y: 2 }; let step = Point { x: 3, y: 4 }; let end = start + step; println!("{:?}", end); println!("{}", end == Point { x: 4, y: 6 });}Ausgabe:
Point { x: 4, y: 6 }trueSub, Mul, Div und Neg funktionieren genauso, alle in std::ops. Weil Point hier Copy ableitet, kannst du start und step nach dem + weiter benutzen. Mehr dazu unter Wichtige Traits und Fortgeschrittene Traits.
Lösung 4: Strings mit + oder format! verbinden
Abschnitt betitelt „Lösung 4: Strings mit + oder format! verbinden“Nimm das, wenn der Fehler cannot add `&str` to `&str` lautet. + auf Strings braucht einen eigenen String links und einen &str rechts. Die Hilfe von rustc sagt es auf ihre Weise: „create an owned String from a string reference“ (mach aus der String-Referenz einen eigenen String), also first.to_owned() + second. Bei zwei Strings leihst du den rechten aus: s1 + &s2. Und format! funktioniert immer und verschiebt nichts:
fn main() { let first = String::from("Hallo, "); let second = String::from("Ferris");
let shout = format!("{first}{second}!"); // leiht beide aus let joined = first + &second; // first wird in joined verschoben let short = "Hi, ".to_owned() + "Krabbe"; // &str links: erst zu eigenem String machen
println!("{shout}"); println!("{joined}"); println!("{short}"); println!("{second} ist noch da");}Ausgabe:
Hallo, Ferris!Hallo, FerrisHi, KrabbeFerris ist noch da+ übernimmt das Ownership (Eigentum) am linken String und hängt an ihn an. Deshalb kannst du first nach dieser Zeile nicht mehr benutzen. Wenn dich das verwirrt, nimm einfach format!. Siehe Strings.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“Vergleiche in generischem Code. Die klassische Funktion largest scheitert mit binary operation `>` cannot be applied to type `&T`, denn T könnte jeder Typ sein, auch einer ohne Reihenfolge:
fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { // ❌ T ist vielleicht nicht vergleichbar largest = item; } } largest}Lösung: Füg eine Trait-Bedingung hinzu, genau wie rustc vorschlägt: fn largest<T: PartialOrd>(list: &[T]) -> &T. Siehe Generics.
Einen Enum-Wert prüfen. if status == Status::Banned schlägt fehl, bis das Enum #[derive(PartialEq)] hat. Kannst du das Enum nicht ändern (weil es aus einem anderen Crate kommt), nimm stattdessen matches!(status, Status::Banned) oder ein match.
Zwei Strings verbinden. first + second mit zwei Strings ist nicht E0369: Es ist E0308 („expected &str, found String“) mit help: consider borrowing here, also first + &second. Gleiche Familie, gleiche Lösung wie in Lösung 4.
Zwei verschiedene Typen vergleichen. count == limit mit einem i32 und einem f64 ergibt E0308, und name == 'F' mit einem String und einem char ergibt E0277 („can’t compare String with char“). Als Faustregel: E0369 bekommst du, wenn der linke Typ gar keine Version des Operators hat. E0308 oder E0277 bekommst du, wenn er eine hat, aber nicht für diesen Typ auf der rechten Seite. Lösung: Wandle eine Seite um (count as f64 == limit, name == "F").
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0277: trait bound not satisfied: Den Operator gibt es für den linken Typ, aber nicht mit diesem Typ auf der rechten Seite (
i32 + f64,String == char). - E0308: mismatched types: Das bekommst du bei
String + Stringoder wenn du zwei verschiedene Structs vergleichst. - E0599: no method found:
.sort()oder.contains()können genauso scheitern, wenn deinem TypOrdoderPartialEqfehlt. E0368: binary assignment operation += cannot be applied: die+=-Version dieses Fehlers. ImplementiereAddAssignausstd::ops.- Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Du schreibst if order_a == order_b für ein Struct Order und bekommst E0369. Was ist meistens die Lösung?
2. Mit #[derive(PartialOrd)] auf struct Version { major: u32, minor: u32 }: Wie werden zwei Versionen verglichen?
3. first und second sind beide &str. Welcher Ausdruck verbindet sie ohne Fehler?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0369 – Index der Rust-Fehlercodes (auf Englisch): die offizielle Erklärung dieses Fehlers.
- Anhang C: Ableitbare Traits – Die Programmiersprache Rust: was dir das Ableiten von
PartialEqundPartialOrdbringt, einschließlich der Reihenfolge Feld für Feld. - UTF-8-kodierten Text in Strings ablegen – Die Programmiersprache Rust: warum
+bei Strings links einenStringund rechts einen&strbraucht und wann duformat!nimmst. - Operator Overloading – Rust by Example (auf Englisch): ein kurzes, ausführbares Beispiel, wie du
Addfür deine eigenen Typen implementierst. std::ops::Add– Doku der Standardbibliothek (auf Englisch): der TraitAdd, sein TypOutputund das Addieren von Werten verschiedener Typen.