Rust-Fehler E0277: the trait bound is not satisfied (Trait fehlt)
Fehler E0277 bedeutet: Du hast einen Typ an einer Stelle benutzt, die eine Fähigkeit braucht, die dieser Typ nicht hat. Ausgeben, vergleichen, addieren, umwandeln, an einen anderen Thread schicken: Für all das braucht es einen Trait (eine Art Fähigkeit), und deinem Typ fehlt genau dieser Trait. E0277 gehört zu den häufigsten Rust-Fehlern. Die Meldung nennt dir meist die genaue Lösung, und oft ist sie nur eine Zeile lang. (Diese Seite gehört zu Rust-Compilerfehler einfach erklärt.)
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“Die Variante, die die meisten zuerst sehen: ein eigenes Struct mit {} ausgeben.
error[E0277]: `Player` doesn't implement `std::fmt::Display` --> src/main.rs:8:20 |8 | println!("{}", p); | -- ^ `Player` cannot be formatted with the default formatter | | | required by this formatting parameter |help: the trait `std::fmt::Display` is not implemented for `Player` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) insteadDas Programm, das den Fehler auslöst:
struct Player { name: String, score: u32,}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{}", p);}Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Ein Trait ist ein Versprechen, dass ein Typ etwas kann. Display ist das Versprechen „Ich weiß, wie ich mich hübsch ausgebe“. PartialEq heißt „Man kann mich mit == vergleichen“. Send heißt „Ich darf sicher an einen anderen Thread gehen“.
Code wie println!, .sort(), + und thread::spawn verlangt eines dieser Versprechen. Der Compiler zeigt auf den Wert, den du übergeben hast (p in Zeile 8), und sagt: Dieser Typ hat das nie versprochen, also kann ich ihn hier nicht benutzen. Die Notiz am Ende gibt dir schon einen Tipp: Mit {:?} könnte es klappen.
Stell dir eine Steckdose vor. Sie nimmt nur Stecker einer bestimmten Form. Dein Gerät funktioniert vielleicht tadellos, aber wenn der Stecker nicht passt, geht er nicht rein. E0277 ist der Compiler, der sagt: „falscher Stecker“. Du behebst das, indem du dem Typ den richtigen Stecker gibst: meistens mit #[derive(...)], manchmal mit einem kurzen impl-Block.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“Rust rät nie. Für deinen eigenen Typ Player gibt es nicht die eine „richtige“ Art, ihn auszugeben, zu vergleichen oder zu sortieren. Sind zwei Spieler gleich, wenn ihre Namen übereinstimmen, oder nur, wenn jedes Feld übereinstimmt? Rust lässt dich das sagen, statt heimlich etwas zu tun, das du gar nicht wolltest.
Die Prüfung passiert beim Kompilieren. In vielen Sprachen bekommst du für ein Objekt, dem du das Ausgeben nie beigebracht hast, etwas Nutzloses wie <Player object at 0x7f...>. Und ein Wert, der nicht thread-sicher ist, führt in einem Thread zu einem seltenen Absturz in Produktion. In Rust sind beides Compilerfehler, die du einmal behebst.
Mehr darüber, wie Traits funktionieren, steht auf der Seite Traits. Die alltäglichen Traits (Debug, Display, PartialEq, Ord, Hash) findest du unter Wichtige Traits.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“E0277 hat viele Gesichter. Such dir unten die Meldung, die zu deiner passt.
Lösung 1: Ein Struct mit {} oder {:?} ausgeben
Abschnitt betitelt „Lösung 1: Ein Struct mit {} oder {:?} ausgeben“{:?} braucht Debug. Den schreibst du fast nie selbst: Füg #[derive(Debug)] hinzu. Das ist für Debugging und Logs gedacht. Die {:?}-Variante des Fehlers sieht so aus:
error[E0277]: `Player` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `Player` = note: add `#[derive(Debug)]` to `Player` or manually `impl Debug for Player`Die Notiz sagt es schon: #[derive(Debug)] hinzufügen oder Debug von Hand implementieren.
{} braucht Display. Das kann Rust nicht ableiten, denn nur du weißt, wie die „hübsche“ Version aussehen soll. Schreib einen kleinen impl mit write!. Das ist für Text gedacht, den deine Benutzer sehen.
use std::fmt;
#[derive(Debug)] // gibt dir {:?} geschenktstruct Player { name: String, score: u32,}
// Display schreibst du selbst: Hier legst du fest, was {} ausgibt.impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} ({} Punkte)", self.name, self.score) }}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{:?}", p); println!("{}", p);}Ausgabe:
Player { name: "Ana", score: 42 }Ana (42 Punkte)Lösung 2: Vergleichen, sortieren oder als HashMap-Schlüssel benutzen
Abschnitt betitelt „Lösung 2: Vergleichen, sortieren oder als HashMap-Schlüssel benutzen“.contains() und == brauchen PartialEq. .sort() braucht Ord. Schlüssel in HashSet und HashMap brauchen Eq und Hash. Die Meldungen sehen so aus:
error[E0277]: can't compare `Point` with `Point` = help: the trait `PartialEq` is not implemented for `Point`
error[E0277]: the trait bound `Priority: Ord` is not satisfied = help: the trait `Ord` is not implemented for `Priority`
error[E0277]: the trait bound `Point: Hash` is not satisfied = help: the trait `Hash` is not implemented for `Point`Jede Meldung nennt den fehlenden Trait. Die Lösung: Leite ihn ab. Die Traits bauen aufeinander auf, deshalb ist das übliche komplette Set PartialEq, Eq, Hash, PartialOrd, Ord:
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]enum Priority { Low, Medium, High,}
fn main() { let mut tasks = vec![Priority::High, Priority::Low, Priority::Medium]; tasks.sort(); // braucht Ord println!("{:?}", tasks); println!("Enthält High: {}", tasks.contains(&Priority::High)); // braucht PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // Schlüssel braucht Eq + Hash for task in &tasks { *counts.entry(*task).or_insert(0) += 1; } println!("Aufgaben mit Low: {}", counts[&Priority::Low]);}Ausgabe:
[Low, Medium, High]Enthält High: trueAufgaben mit Low: 1Ein abgeleitetes Ord sortiert Enum-Varianten in der Reihenfolge, in der du sie geschrieben hast, und Structs Feld für Feld von oben nach unten. Ein ehrlicher Haken: Ein Typ mit einem f64-Feld kann Eq, Hash und Ord nicht ableiten. Gleitkommazahlen haben nämlich NaN, und NaN ist nicht einmal sich selbst gleich. Wie du Gleitkommazahlen sortierst, steht unter Sortieren.
Lösung 3: Zahlentypen mischen (cannot add f64 to i32)
Abschnitt betitelt „Lösung 3: Zahlentypen mischen (cannot add f64 to i32)“error[E0277]: cannot add `f64` to `i32` --> src/main.rs:4:23 |4 | let total = items + price; | ^ no implementation for `i32 + f64` | = help: the trait `Add<f64>` is not implemented for `i32`Die Meldung heißt „ein f64 kann nicht zu einem i32 addiert werden“. Rust wandelt Zahlen nie für dich um, nicht einmal i32 nach f64. Beide Seiten von +, -, * und / müssen denselben Typ haben. Wandle eine Seite selbst mit as um:
fn main() { let items: i32 = 3; let price: f64 = 2.5; let total = items as f64 * price; // erst umwandeln, dann multiplizieren println!("Gesamt: {total}");}Ausgabe:
Gesamt: 7.5f64::from(items) funktioniert auch und kann nie Daten verlieren. In die andere Richtung (f64 nach i32) werden die Nachkommastellen abgeschnitten, also überleg dir vorher, wie du runden willst. Die Seiten Typumwandlung und Konvertierungen erklären die Details.
Lösung 4: ? couldn’t convert the error
Abschnitt betitelt „Lösung 4: ? couldn’t convert the error“error[E0277]: `?` couldn't convert the error to `ConfigError` |8 | fn parse_port(text: &str) -> Result<u16, ConfigError> { | ------------------------ expected `ConfigError` because of this9 | let port = text.parse::<u16>()?; | --------------^ the trait `From<ParseIntError>` is not implemented for `ConfigError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` traitWenn ? auf einen Fehler trifft, gibt es ihn aus deiner Funktion zurück. Ist der Fehlertyp ein anderer als der, den deine Funktion verspricht, versucht ?, ihn mit From umzuwandeln – genau das sagt die Notiz am Ende. Deine Aufgabe ist es, dieses From zu schreiben:
use std::num::ParseIntError;
#[derive(Debug)]enum ConfigError { BadPort(ParseIntError),}
// Bring `?` bei, wie aus einem ParseIntError ein ConfigError wird.impl From<ParseIntError> for ConfigError { fn from(err: ParseIntError) -> Self { ConfigError::BadPort(err) }}
fn parse_port(text: &str) -> Result<u16, ConfigError> { let port = text.parse::<u16>()?; Ok(port)}
fn main() { println!("{:?}", parse_port("8080")); println!("{:?}", parse_port("achtzig"));}Ausgabe:
Ok(8080)Err(BadPort(ParseIntError { kind: InvalidDigit }))Andere gute Möglichkeiten: Wandle nur einen einzelnen Aufruf mit .map_err(ConfigError::BadPort)? um, nutze #[from] aus dem Crate thiserror, oder gib in kleinen Programmen Result<T, Box<dyn std::error::Error>> zurück. Siehe Eigene Fehlertypen und anyhow und thiserror.
Lösung 5: Rc cannot be sent between threads safely
Abschnitt betitelt „Lösung 5: Rc cannot be sent between threads safely“error[E0277]: `Rc<String>` cannot be sent between threads safely --> src/main.rs:6:32 |6 | let handle = thread::spawn(move || { | ------------- required by a bound introduced by this call ... = help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<String>`Die Meldung heißt „Rc<String> kann nicht sicher zwischen Threads verschickt werden“. Rc zählt seine Besitzer mit einer gewöhnlichen Zahl. Zwei Threads könnten sie gleichzeitig ändern und dabei kaputtmachen. Deshalb ist Rc nicht Send. Nimm stattdessen Arc (die „atomare“ Version). Es funktioniert genauso und ist über Threads hinweg sicher:
use std::sync::Arc;use std::thread;
fn main() { let config = Arc::new(String::from("debug")); let for_thread = Arc::clone(&config); // ein zweiter Besitzer für den Thread
let handle = thread::spawn(move || { println!("Thread sieht: {for_thread}"); }); handle.join().unwrap();
println!("Main hat noch: {config}");}Ausgabe:
Thread sieht: debugMain hat noch: debugDasselbe gilt für RefCell: Über Threads hinweg nimmst du Arc<Mutex<T>> statt Rc<RefCell<T>>. Siehe Geteilter Zustand und Nebenläufigkeit.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“- Generische Funktionen.
fn announce<T: Display>(item: T), aufgerufen mit deinem Struct, ergibt`Song` doesn't implement `std::fmt::Display`mit dem Hinweis „required by a bound inannounce“ (verlangt durch eine Bedingung inannounce). Lösung: Implementiere den Trait, den die Bedingung verlangt. - Tests.
assert_eq!(a, b)braucht sowohlPartialEqals auchDebug(um die Werte auszugeben, wenn der Test fehlschlägt). Lösung:#[derive(Debug, PartialEq)]. .collect()in den falschen Typ.let nums: Vec<i32> = words.iter().collect();ergibt „a value of typeVec<i32>cannot be built from an iterator over elements of type&String“. EinVec<i32>lässt sich also nicht aus&String-Elementen bauen. Lösung: Wandle zuerst jedes Element um, zum Beispiel mit.map(|w| w.parse().unwrap()). Siehe Iteratoren.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0599: no method found: Ein
.insert()auf einerHashMap, deren Schlüssel nichtHashist, zeigt sich manchmal stattdessen als E0599 („trait bounds were not satisfied“). Gleiche Lösung: Leite die Traits ab. - E0308: mismatched types: Du hast einen Typ übergeben, wo ein anderer erwartet wird, etwa ein
i32, wo einf64hingehört. - E0382: borrow of moved value: taucht oft gleich danach auf, wenn du beim Nachdenken über Traits
CloneoderCopyhinzufügst. E0369: binary operation == cannot be applied: die Version des fehlendenPartialEqfür den Operator==. Gleiche Lösung.- Alle Compilerfehler einfach erklärt
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Du bekommst „Order doesn't implement Debug“, wenn du {:?} benutzt. Was ist die schnellste Lösung?
2. Warum schlägt let total = 3_i32 + 2.5_f64; fehl?
3. Dein Thread-Code scheitert mit „Rc<String> cannot be sent between threads safely“. Was solltest du benutzen?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0277 – Index der Rust-Fehlercodes (auf Englisch): die offizielle Erklärung dieses Fehlers.
- Gemeinsames Verhalten definieren mit Traits – Die Programmiersprache Rust: was Traits sind und wie Trait-Bedingungen (Trait Bounds) funktionieren.
- Anhang C: Ableitbare Traits – Die Programmiersprache Rust: was dir
Debug,PartialEq,Eq,Hash,PartialOrdundOrdjeweils bringen, wenn du sie ableitest. - Behebbare Fehler mit Result – Die Programmiersprache Rust: wie
?funktioniert, einschließlich der Umwandlung mitFrom. Send– Doku der Standardbibliothek (auf Englisch): welche Typen zwischen Threads wechseln dürfen und warumRces nicht darf.