Zum Inhalt springen

Rust-Fehler E0004: non-exhaustive patterns not covered (match unvollständig)

E0004 bedeutet: Dein match hat mindestens einen möglichen Wert vergessen, und Rust weigert sich zu raten, was in diesem Fall passieren soll. Das ist einer der freundlichsten Fehler in Rust: Der Compiler sagt dir genau, welcher Fall fehlt, und schreibt dir sogar die passende Zeile vor. Diese Seite zeigt dir zuerst die Lösung und erklärt dann, warum diese Prüfung eine der besten Eigenschaften von Rust ist. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.

error[E0004]: non-exhaustive patterns: `OrderStatus::Delivered` not covered
--> src/main.rs:8:11
|
8 | match status {
| ^^^^^^ pattern `OrderStatus::Delivered` not covered
|
note: `OrderStatus` defined here
--> src/main.rs:1:6
|
1 | enum OrderStatus {
| ^^^^^^^^^^^
...
4 | Delivered, // später hinzugefügt
| --------- not covered
= note: the matched value is of type `OrderStatus`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
10 ~ OrderStatus::Shipped => "on its way",
11 ~ OrderStatus::Delivered => todo!(),
|

„Non-exhaustive patterns“ heißt „nicht erschöpfende Muster“: Die Muster in deinem match decken nicht alle Werte ab, und OrderStatus::Delivered ist „not covered“ (nicht abgedeckt). Das help schlägt vor, einen Arm mit einem Platzhalter _ oder genau dem fehlenden Muster zu ergänzen.

Dieses Programm erzeugt den Fehler:

enum OrderStatus {
Pending,
Shipped,
Delivered, // später hinzugefügt
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "waiting to be packed",
OrderStatus::Shipped => "on its way",
// ❌ nichts für Delivered
}
}
fn main() {
println!("{}", describe(OrderStatus::Delivered));
}

Der Text zwischen den Backticks hängt von deinem Code ab. Du siehst vielleicht auch `None` not covered, `&_` not covered, `0_u8` and `4_u8..=u8::MAX` not covered oder `(true, false)` and `(false, true)` not covered. Es ist jedes Mal derselbe Fehler: Rust zählt die Werte auf, für die dein match keinen Arm hat.

Lies die Meldung von oben nach unten:

  1. Zeile 8: Die ^^^^^^ zeigen auf den Wert, den du abgleichst: status.
  2. „pattern OrderStatus::Delivered not covered“: Das ist der Fall, den du vergessen hast.
  3. Die note springt zur Definition des Enums und markiert in Zeile 4 die vergessene Variante.
  4. Das help zeigt genau den Arm, den du ergänzen musst. todo!() ist ein Platzhalter, den du durch echten Code ersetzt.

Stell dir einen Getränkeautomaten mit drei Knöpfen vor, von denen nur zwei angeschlossen sind. Was passiert, wenn jemand den dritten drückt? Rust lässt dich keinen Automaten mit einem toten Knopf ausliefern. Jeder Knopf braucht eine Antwort, selbst wenn die Antwort „außer Betrieb“ lautet.

Ein match muss in Rust erschöpfend sein (auf Englisch „exhaustive“): Für jeden Wert, den der Typ haben kann, muss genau ein Arm passen. Würde ein Arm fehlen, käme das Programm an eine Stelle, an der es keinen Code zum Ausführen gibt. Andere Sprachen fallen dann still durch, geben einen Standardwert zurück oder stürzen zur Laufzeit ab. Rust erwischt das, bevor das Programm überhaupt läuft. Die Grundlagen erklärt die Lektion match.

Der eigentliche Gewinn kommt später, wenn dein Code wächst. Angenommen, du fügst einem Enum, das an zehn Stellen in deinem Projekt abgeglichen wird, eine neue Variante hinzu:

enum OrderStatus {
Pending,
Shipped,
Delivered,
Returned, // neu!
}

Der Compiler meldet dir jetzt ein E0004 für jedes match, das du anpassen musst:

error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:9:11
error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:17:11

Das ist eine To-do-Liste, die jemand für dich geschrieben hat. Du musst nicht die ganze Codebasis durchsuchen und hoffen, dass du jede Stelle gefunden hast. Deshalb mögen erfahrene Rust-Entwickler Enums so sehr: Du kannst sie ohne Angst ändern.

Nimm diese Lösung für deine eigenen Enums. In den meisten Fällen ist das die richtige Wahl, und genau das schlägt das help: von rustc vor. Ergänze den Arm und ersetze todo!() durch das, was wirklich passieren soll:

enum OrderStatus {
Pending,
Shipped,
Delivered,
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "waiting to be packed",
OrderStatus::Shipped => "on its way",
OrderStatus::Delivered => "at your door", // ✅ jeder Fall behandelt
}
}
fn main() {
println!("{}", describe(OrderStatus::Pending));
println!("{}", describe(OrderStatus::Delivered));
}

Ausgabe:

waiting to be packed
at your door
▶ Selbst ausprobieren

Die Texte bleiben hier auf Englisch, damit du sie direkt mit der Fehlermeldung oben vergleichen kannst. Sollen mehrere Varianten dasselbe tun, verbinde sie mit | in einem Arm: OrderStatus::Shipped | OrderStatus::Delivered => false. Du nennst trotzdem jede Variante beim Namen, also kann der Compiler dich weiterhin auf neue hinweisen. Dasselbe gilt für Option: Sagt der Fehler `None` not covered, ergänze einen Arm None => .... Siehe Option.

Nimm diese Lösung, wenn du Ganzzahlen, char oder Strings abgleichst. Ein u8 hat 256 mögliche Werte und ein &str unendlich viele, du kannst sie also nicht alle aufzählen. Nimm Bereiche wie 1..=9 für Gruppen von Zahlen und schließ mit _ (oder einem Namen wie other) für „alles andere“ ab:

fn grade(score: u8) -> char {
match score {
90..=100 => 'A',
75..=89 => 'B',
50..=74 => 'C',
0..=49 => 'F',
_ => '?', // 101 bis 255: keine echte Punktzahl
}
}
fn run(command: &str) {
match command {
"start" => println!("Timer startet"),
"stop" => println!("Timer stoppt"),
other => println!("Unbekannter Befehl: {other}"),
}
}
fn main() {
println!("{} {} {}", grade(95), grade(62), grade(150));
run("start");
run("pause");
}

Ausgabe:

A C ?
Timer startet
Unbekannter Befehl: pause
▶ Selbst ausprobieren

Bei Strings sagt rustc es sogar direkt: `&str` cannot be matched exhaustively, so a wildcard `_` is necessary („&str lässt sich nicht erschöpfend abgleichen, also ist ein Platzhalter _ nötig“). Bei Zahlen prüft Rust deine Bereiche genau. Decken sie wirklich jeden Wert ab (zum Beispiel 0..=49 bis 90..=u8::MAX), brauchst du gar kein _. Mehr dazu unter Einen Bereich abgleichen.

Lösung 3: if let oder let else, wenn dich nur ein Fall interessiert

Abschnitt betitelt „Lösung 3: if let oder let else, wenn dich nur ein Fall interessiert“

Nimm diese Lösung, wenn du nur auf ein Muster reagieren und den Rest ignorieren willst. Ein vollständiges match mit einem leeren „mach nichts“-Arm funktioniert auch, aber if let drückt es klarer aus. let else ist für „hol den Wert heraus oder verlass die Funktion früh“:

fn greet(nickname: Option<&str>) {
if let Some(name) = nickname {
println!("Hallo, {name}!");
}
// kein Spitzname: nichts tun
}
fn first_word_len(text: &str) -> usize {
let Some(word) = text.split_whitespace().next() else {
return 0; // leerer Text: früh zurückkehren
};
word.len()
}
fn main() {
greet(Some("Ferris"));
greet(None);
println!("{}", first_word_len("hello world"));
println!("{}", first_word_len(" "));
}

Ausgabe:

Hallo, Ferris!
5
0
▶ Selbst ausprobieren

Der else-Block eines let else muss den aktuellen Block verlassen (return, break, continue oder ein Panic). Siehe if let und let-else.

Lösung 4: Ein Auffang-Arm _ => für den Rest (mit einem Haken)

Abschnitt betitelt „Lösung 4: Ein Auffang-Arm _ => für den Rest (mit einem Haken)“

Nimm diese Lösung, wenn die meisten Varianten eines Enums wirklich dasselbe tun. _ passt auf alles, was die Arme darüber nicht abgedeckt haben:

enum Key {
Up,
Down,
Left,
Right,
Enter,
Escape,
}
fn handle(key: Key) {
match key {
Key::Enter => println!("Bestätigen"),
Key::Escape => println!("Abbrechen"),
_ => println!("Cursor bewegen"),
}
}
fn main() {
handle(Key::Enter);
handle(Key::Left);
handle(Key::Escape);
}

Ausgabe:

Bestätigen
Cursor bewegen
Abbrechen
▶ Selbst ausprobieren

Sei ehrlich zu dir, was das kostet: _ schaltet die Prüfung aus Warum Rust das macht ab. Fügst du später Key::Delete hinzu, bleibt der Compiler still, und ein Druck auf Entf bewegt den Cursor. Das ist ein Bug, den Rust für dich hätte finden können. Bei deinen eigenen Enums zählst du die Varianten besser mit | auf (Key::Up | Key::Down | Key::Left | Key::Right => ...). Heb dir _ für Zahlen, Strings und Enums aus anderen Crates auf.

Option ohne None. match nickname { Some(name) => ... } ergibt `None` not covered. Lösung: Ergänze einen Arm None =>, oder nimm if let Some(name) = nickname, wenn es für None nichts zu tun gibt.

Tupel und Wahrheitswerte. Gleichst du zwei bool auf einmal ab, gibt es vier Kombinationen, und man vergisst leicht welche:

match (logged_in, is_admin) {
(true, true) => println!("Willkommen, Admin"),
(false, false) => println!("Bitte melde dich an"),
} // ❌ patterns `(true, false)` and `(false, true)` not covered

Lösung: Ergänze die fehlenden Arme, oder benutze _ im Tupel, etwa (false, _) => println!("Bitte melde dich an"). Das deckt beide Fälle „nicht angemeldet“ ab. Siehe Mehrere Werte auf einmal abgleichen.

Ein Muster in let. let Some(score) = saved; ist ein let, das vielleicht nicht passt. Das ist ein Schwesterfehler, E0005, mit derselben Idee:

error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Some(score) = saved;
| ^^^^^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
help: you might want to use `let...else` to handle the variant that isn't matched
|
3 | let Some(score) = saved else { todo!() };
| ++++++++++++++++

„Refutable pattern“ heißt „abweisbares Muster“, also eines, das fehlschlagen kann. Die note erklärt: Ein let braucht ein Muster, das immer passt. Das help schlägt let...else vor. Lösung: let Some(score) = saved else { return; };, oder if let, wie in Lösung 3. Siehe Muster in let.

Enums aus anderen Crates. Manche Enums, wie std::io::ErrorKind, sind als #[non_exhaustive] markiert. Ihre Autoren dürfen später Varianten hinzufügen. Selbst wenn du alle Varianten aufzählst, die es heute gibt, sagt rustc deshalb `_` not covered. Lösung: Ergänze einen Arm _ =>. Hier ist er Pflicht, keine Abkürzung.

  • E0005 (refutable pattern in local binding, abweisbares Muster in let): dieselbe „not covered“-Prüfung, nur für let statt match. Oben gezeigt.
  • E0308: unpassende Typen, zum Beispiel wenn die Arme eines match verschiedene Typen zurückgeben ("text" in einem Arm, eine Zahl in einem anderen).
  • E0382: borrow of partially moved value (teilweise verschobener Wert), wenn ein Arm wie Some(name) einen String herausverschiebt und du das Original danach noch benutzt. Gleich stattdessen &nickname ab.
  • E0507: cannot move out of a borrowed value, wenn ein Arm einen String aus etwas herausnehmen will, das du nur ausgeliehen hast.
  • Alle Compilerfehler: die vollständige Liste der Fehlerseiten.

Kurzer Check

1. Du fügst einem Enum die Variante Refunded hinzu. Was macht der Compiler mit deinen bestehenden match-Ausdrücken, die jede Variante beim Namen nennen?

2. Du gleichst einen &str mit "yes" und "no" ab. Wie behebst du E0004?

3. Was ist der Nachteil von _ => in einem match über dein eigenes Enum?

Punkte: 0 / 3