Zum Inhalt springen

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.

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`: PartialOrd fehlt (auch bei >, <=, >=).
  • cannot add `Point` to `Point`: Add fehlt. Du siehst auch „cannot subtract“, „cannot multiply“ und so weiter.
  • cannot add `&str` to `&str`: Du wolltest zwei String-Slices mit + verbinden.

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.

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.

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 }: true
▶ Selbst ausprobieren

Jedes 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.

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: true
▶ Selbst ausprobieren

Willst du einen Vec deines Typs mit .sort() sortieren, brauchst du zusätzlich Eq und Ord. Siehe Sortieren.

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 }
true
▶ Selbst ausprobieren

Sub, 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.

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, Ferris
Hi, Krabbe
Ferris ist noch da
▶ Selbst ausprobieren

+ ü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.

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").

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