Zum Inhalt springen

Panic in Rust

Eine Panic ist Rusts Art, ein Programm sofort anzuhalten, wenn etwas so schiefgelaufen ist, dass es sich nicht mehr retten lässt. Wenn du die bisherigen Seiten mitgemacht hast, hast du wahrscheinlich schon ein paar ausgelöst, ohne es zu wollen: Ein Index hinter dem Ende eines Arrays oder .unwrap() auf einem None lösen beide eine Panic aus.

fn main() {
panic!("etwas ist gründlich schiefgelaufen");
}
thread 'main' (12345) panicked at src/main.rs:2:5:
etwas ist gründlich schiefgelaufen
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Die Zahl in Klammern, hier (12345), ist die ID des Threads. Sie ist bei jedem Lauf anders, und ältere Rust-Versionen zeigen sie gar nicht an.

▶ Selbst ausprobieren

Wenn das läuft, gibt das Programm diese Meldung aus und hält an. Nach dem panic! wird nichts mehr ausgeführt. Die erste und die letzte Zeile stammen von Rust selbst und sind deshalb auf Englisch: Der Thread main ist an der genannten Stelle in Panik geraten, und mit der Umgebungsvariable RUST_BACKTRACE=1 bekommst du einen Backtrace – also die Liste der Funktionsaufrufe, die zur Panic geführt haben.

Was eine Panic auslöst, ohne dass du panic! aufrufst

Abschnitt betitelt „Was eine Panic auslöst, ohne dass du panic! aufrufst“

Ein paar Operationen, die du schon kennst, lösen von selbst eine Panic aus, wenn etwas nicht stimmt. Jedes Beispiel unten bekommt den riskanten Wert über einen Funktionsparameter statt als feste Zahl im Code. Der Grund: Steht der Index oder die 0 direkt im Code, erkennt der Compiler oft schon beim Kompilieren, dass die Operation immer scheitert. Dann baut er das Programm gar nicht erst, statt es laufen und in Panik geraten zu lassen.

fn get(numbers: &[i32], index: usize) -> i32 {
numbers[index] // Panic: Index außerhalb des gültigen Bereichs
}
fn main() {
let numbers = [1, 2, 3];
println!("{}", get(&numbers, 10));
}
▶ Selbst ausprobieren
fn main() {
let value: Option<i32> = None;
println!("{}", value.unwrap()); // Panic: called `Option::unwrap()` on a `None` value
}
▶ Selbst ausprobieren
fn divide(a: i32, b: i32) -> i32 {
a / b // Panic: attempt to divide by zero
}
fn main() {
let a = 10;
let b = 0;
println!("{}", divide(a, b));
}
▶ Selbst ausprobieren

Die Meldungen in den Kommentaren zeigen, was Rust auf Englisch ausgibt: unwrap() wurde auf einem None-Wert aufgerufen bzw. es wurde versucht, durch null zu teilen. In jedem dieser Fälle weigert sich Rust, dein Programm in einem Zustand weiterlaufen zu lassen, der keinen Sinn ergibt – statt still irgendeinen Unsinn zurückzugeben.

Diese Entscheidung triffst du oft: Soll dieser Fehlschlag das Programm abbrechen, oder soll der Aufrufer die Chance bekommen, ihn zu behandeln?

  • panic! – für Bugs und Situationen, die bei korrektem Code nie vorkommen dürften. Es gibt keinen sinnvollen Weg, sich davon zu erholen. Sofort anzuhalten (bevor noch mehr Schaden entsteht), ist dann die sicherste Wahl.
  • Result (siehe vorherige Seite) – für Fehlschläge, mit denen ein Aufrufer rechnen kann und die er behandeln möchte: falsche Benutzereingaben, eine fehlende Datei oder eine gescheiterte Netzwerkanfrage.
// Falsche Eingabe vom Benutzer – davon kann man sich erholen, also Result
fn parse_age(input: &str) -> Result<u32, String> {
input.parse().map_err(|_| String::from("kein gültiges Alter"))
}
// Eine Bedingung, die bei korrektem Code nie verletzt sein kann – also panic
fn get_first(numbers: &[i32]) -> i32 {
if numbers.is_empty() {
panic!("get_first wurde mit einem leeren Slice aufgerufen");
}
numbers[0]
}

Du hast .unwrap() schon bei Option und bei Result gesehen. Eigentlich ist es nur eine Abkürzung für „Gib mir den Wert – oder lös eine Panic aus, wenn keiner da ist.“ .expect("Meldung") macht dasselbe, lässt dich aber eine hilfreichere Meldung mitgeben:

fn main() {
let input = "42";
let number: i32 = input.parse().expect("die Eingabe sollte eine gültige Zahl sein");
println!("{number}");
}
▶ Selbst ausprobieren

Schlägt die Umwandlung fehl, enthält die Panic-Meldung deinen Text. Das macht die Fehlersuche viel leichter als die allgemeine Meldung, die .unwrap() liefert.

Annahmen prüfen: assert!, assert_eq! und unreachable!

Abschnitt betitelt „Annahmen prüfen: assert!, assert_eq! und unreachable!“

Manchmal weißt du, dass an einer bestimmten Stelle im Code etwas wahr sein muss. Rust hat ein paar kleine Makros, die aus dieser Überzeugung eine Prüfung machen. Schlägt die Prüfung fehl, lösen sie eine Panic mit einer klaren Meldung aus. So zeigt sich ein Bug genau dort, wo er entsteht, statt später für seltsame Ergebnisse zu sorgen.

  • assert!(condition, "message") löst eine Panic aus, wenn die Bedingung false ist.
  • assert_eq!(left, right) löst eine Panic aus, wenn die beiden Werte nicht gleich sind, und gibt beide aus.
  • unreachable!("message") markiert Code, der nie laufen sollte, zum Beispiel einen match-Zweig für einen Wert, den es nicht geben kann.
fn average(numbers: &[i32]) -> i32 {
assert!(!numbers.is_empty(), "average braucht mindestens eine Zahl");
let mut total = 0;
for n in numbers {
total += n;
}
total / numbers.len() as i32
}
fn day_name(day: u8) -> &'static str {
match day {
1 => "Montag",
2 => "Dienstag",
3 => "Mittwoch",
4 => "Donnerstag",
5 => "Freitag",
6 => "Samstag",
7 => "Sonntag",
_ => unreachable!("day muss zwischen 1 und 7 liegen, war aber {day}"),
}
}
fn main() {
let avg = average(&[70, 80, 90]);
assert_eq!(avg, 80);
println!("Durchschnitt: {avg}");
println!("Tag 3 ist {}", day_name(3));
}

Ausgabe:

Durchschnitt: 80
Tag 3 ist Mittwoch
▶ Selbst ausprobieren

Hier bestehen alle Prüfungen, also läuft das Programm ganz normal. Ändere jetzt im Playground die Liste zu [70, 80, 95]. Der Durchschnitt wird 81, und assert_eq! hält das Programm an und zeigt beide Werte nebeneinander:

thread 'main' (12345) panicked at src/main.rs:26:5:
assertion `left == right` failed
left: 81
right: 80

Diese Meldung kommt von Rust selbst und ist deshalb auf Englisch: Die Prüfung „left == right“ ist fehlgeschlagen, links steht der berechnete Wert, rechts der erwartete. Probier auch average(&[]) oder day_name(9) aus, dann siehst du deine eigenen Meldungen in der Panic.

Diese Prüfungen laufen immer, auch in einem Release-Build. Halte sie also billig. assert_eq! begegnet dir auf der Seite Testen wieder: Dort ist es der wichtigste Weg, mit dem ein Test sagt „das hier sollte gleich dem da sein“. Außerdem gibt es todo!(). Das löst eine Panic mit „not yet implemented“ (noch nicht implementiert) aus. Es ist praktisch als Platzhalter-Rumpf, während du eine Funktion skizzierst, die du später fertig schreibst.

Du hast einen Konfigurationswert let port_text = "8080";. Mach daraus mit .parse() und .expect(...) ein u16, mit einer Meldung, die erklärt, was schiefgelaufen ist. Gib dann Lausche auf Port 8080 aus. Tipp: Die Variable braucht einen Typ, let port: u16 = ..., damit parse weiß, was es erzeugen soll. Ändere den Text danach zu "80a0", um deine Meldung in der Panic zu sehen.

Lösung anzeigen
fn main() {
let port_text = "8080";
let port: u16 = port_text
.parse()
.expect("der Port in der Konfiguration sollte eine Zahl von 0 bis 65535 sein");
println!("Lausche auf Port {port}");
}

Ausgabe:

Lausche auf Port 8080
▶ Selbst ausprobieren

Schreib fn split_bill(total: u32, people: u32) -> u32, die den Anteil jeder Person zurückgibt. Eine Rechnung durch null Personen zu teilen, ist ein Bug im aufrufenden Code. Fang ihn deshalb mit assert! und einer klaren Meldung ab. Gib den Anteil für einen Betrag von 120 und 4 Personen aus. Tipp: Schreib das assert! in die erste Zeile der Funktion, vor die Division.

Lösung anzeigen
fn split_bill(total: u32, people: u32) -> u32 {
assert!(people > 0, "split_bill braucht mindestens eine Person");
total / people
}
fn main() {
let share = split_bill(120, 4);
println!("Jede Person zahlt {share}");
}

Ausgabe:

Jede Person zahlt 30
▶ Selbst ausprobieren
  • panic! hält das Programm sofort an und gibt eine Fehlermeldung aus.
  • Manche Standardoperationen (ein ungültiger Array-Index, .unwrap() auf None, Division durch null) lösen automatisch eine Panic aus.
  • Nimm panic! für Bugs und Bedingungen, die nie verletzt sein dürften; nimm Result für Fehlschläge, die ein Aufrufer sinnvoll behandeln kann.
  • .expect("Meldung") ist wie .unwrap(), aber mit einer Meldung, die die Fehlersuche bei einer Panic erleichtert.
  • assert!, assert_eq! und unreachable! lösen absichtlich eine Panic aus, wenn sich etwas, das du für wahr gehalten hast, als falsch herausstellt.

Kurzer Check

1. Was passiert, wenn du panic! aufrufst?

2. Wann solltest du laut der Faustregel auf dieser Seite panic! statt Result nehmen?

3. Was ist der Unterschied zwischen .unwrap() und .expect('Meldung')?

Punkte: 0 / 3