Nicht behebbare Fehler mit panic!
Manchmal passieren schlimme Dinge in deinem Code und du kannst nichts dagegen
tun. Für diese Fälle hat Rust das Makro panic!
. In der Praxis gibt es zwei
Möglichkeiten, ein Programm abstürzen zu lassen: Durch eine Aktion, die unseren
Code abstürzen lässt (z.B. Zugriff auf ein Array über das Ende hinaus) oder
durch den expliziten Aufruf des Makros panic!
. In beiden Fällen brechen wir
unser Programm ab. Standardmäßig geben diese Programmabbrüche eine
Fehlermeldung aus, räumen den Stapelspeicher auf und beenden sich. Über eine
Umgebungsvariable kannst du auch festlegen, dass Rust den Stapelspeicher
anzeigt, wenn das Programm abbricht, damit du die Quelle des Abbruchs leichter
aufspüren kannst.
Auflösen des Stapelspeichers oder Abbrechen als Fehlerreaktion
Wenn ein Programmabbruch auftritt, beginnt das Programm standardmäßig mit dem Abwickeln, was bedeutet, dass Rust den Stapelspeicher wieder nach oben geht und die Daten aller Funktion aufräumt. Allerdings ist dieses Zurückgehen und Aufräumen eine Menge Arbeit. Rust bietet dir als Alternative daher an, das Programm sofort abzubrechen, also das Programm ohne Aufräumen zu beenden.
Der Speicher, den das Programm benutzt hat, muss dann vom Betriebssystem aufgeräumt werden. Wenn du in deinem Projekt die resultierende Binärdatei so klein wie möglich machen willst, kannst du für ein vorzeitiges Programmende vom Abwickeln zum sofortigen Abbrechen umschalten, indem du
panic = 'abort'
in den entsprechenden[profile]
-Abschnitten in deiner Cargo.toml-Datei hinzufügst. Wenn du beispielsweise im Freigabemodus (release mode) im Fehlerfall sofort abbrechen möchtest, füge dies hinzu:[profile.release] panic = 'abort'
Versuchen wir panic!
in einem einfachen Programm aufzurufen:
Dateiname: src/main.rs
fn main() { panic!("abstürzen und verbrennen"); }
Wenn du das Programm ausführst, wirst du in etwa das hier sehen:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:2:5:
abstürzen und verbrennen
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Der Aufruf von panic!
verursacht die in den letzten beiden Zeilen enthaltene
Fehlermeldung. Die erste Zeile zeigt unsere Fehlermeldung und die Position in
unserem Quellcode, an der der Fehler aufgetreten ist: src/main.rs:2:5 gibt
an, dass es sich um die zweite Zeile und dem fünften Zeichen in unserer Datei
src/main.rs handelt.
In diesem Fall ist die angegebene Zeile Teil unseres Codes und wenn wir uns
diese Zeile ansehen, sehen wir den Makroaufruf panic!
. In anderen Fällen
könnte der Aufruf von panic!
in Code erfolgen, den unser Code aufruft, und
der Dateiname und die Zeilennummer in der Fehlermeldung gehören zu Code von
jemand anderen, der das Makro panic!
aufruft, nicht zu unserem Code, der
schließlich zum Aufruf von panic!
geführt hat.
Wir können die Aufrufhistorie (backtrace) der Funktionen, von der der
panic!
-Aufruf kam, nutzen, um den Codeteil zu ermitteln, der das Problem
verursacht. Um zu verstehen, wie man eine panic!
-Aufrufhistorie liest, lass
uns ein anderes Beispiel betrachten, bei dem ein panic!
-Aufruf aufgrund eines
Fehlers in unserem Code von einer Bibliothek kommt, anstatt von unserem Code,
der das Makro direkt aufruft. Codeblock 9-1 enthält einen Code, der versucht,
auf einen Index in einem Vektor zuzugreifen, der außerhalb des Bereichs
gültiger Indizes liegt.
Dateiname: src/main.rs
fn main() { let v = vec![1, 2, 3]; v[99]; }
Hier versuchen wir, auf das 100. Element unseres Vektors zuzugreifen (das bei
Index 99 liegt, weil die Indexierung bei Null beginnt), der Vektor hat aber nur
drei Elemente. In dieser Situation wird Rust das Programm abbrechen. Das
Verwenden von []
soll ein Element zurückgeben, aber wenn du einen ungültigen
Index übergibst, gibt es kein Element, das Rust hier korrekterweise zurückgeben
könnte.
In C ist der Versuch, über das Ende einer Datenstruktur hinaus zu lesen, ein undefiniertes Verhalten. Möglicherweise erhältst du den Wert im Speicher an der der Datenstruktur entsprechenden Stelle, selbst wenn der Speicher nicht zu dieser Struktur gehört. Dies wird als Hinauslesen über den Puffer (buffer overread) bezeichnet und kann zu Sicherheitslücken führen, wenn ein Angreifer in der Lage ist, den Index so zu manipulieren, dass er unerlaubterweise Daten lesen kann, die nach der Datenstruktur gespeichert sind.
Um dein Programm vor dieser Art Verwundbarkeit zu schützen, wird Rust beim Versuch, ein Element an einem Index zu lesen, der nicht existiert, die Ausführung stoppen und die Fortsetzung verweigern. Versuchen wir es und sehen, was passiert:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Dieser Fehler weist auf Zeile 4 in unserer main.rs
hin, wo wir versuchen, auf
den Index 99
des Vektors in v
zuzugreifen.
Die Zeile note:
sagt uns, dass wir die Umgebungsvariable RUST_BACKTRACE
setzen können, um eine Aufrufhistorie zu erhalten, was genau passiert ist und
den Fehler verursacht hat. Eine Aufrufhistorie ist eine Liste aller
Funktionen, die aufgerufen wurden, um an diesen Punkt zu gelangen.
Aufrufhistorien in Rust funktionieren wie in anderen Sprachen: Der Schlüssel
zum Lesen der Aufrufhistorie ist, von oben zu beginnen und zu lesen, bis du
Dateien siehst, die du geschrieben hast. Das ist die Stelle, an der das Problem
entstanden ist. Die Zeilen darüber sind Code, den dein Code aufgerufen hat; die
Zeilen darunter sind Code, der deinen Code aufgerufen hat. Diese Zeilen können
Core-Rust-Code, Code der Standardbibliothek oder Kisten, enthalten, die du
verwendest. Versuchen wir, eine Aufrufhistorie zu erhalten, indem wir die
Umgebungsvariable RUST_BACKTRACE
auf einen beliebigen Wert außer 0
setzen.
Codeblock 9-2 zeigt eine ähnliche Ausgabe wie die, die du sehen wirst.
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
0: rust_begin_unwind
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/std/src/panicking.rs:645:5
1: core::panicking::panic_fmt
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/core/src/panicking.rs:72:14
2: core::panicking::panic_bounds_check
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/core/src/panicking.rs:208:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/core/src/slice/index.rs:255:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/core/src/slice/index.rs:18:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/alloc/src/vec/mod.rs:2770:9
6: panic::main
at ./src/main.rs:4:6
7: core::ops::function::FnOnce::call_once
at /rustc/07dca489ac2d933c78d3c5158e3f43/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Das ist eine lange Ausgabe! Die genaue Ausgabe kann je nach Betriebssystem und
Rust-Version unterschiedlich sein. Um Aufrufhistorien mit diesen Informationen
zu erhalten, müssen Fehlersuchinfos (debug symbols) aktiviert sein.
Fehlersuchinfos sind standardmäßig aktiviert, wenn du cargo build
oder
cargo run
ohne Flag --release
verwendest, wie wir es hier haben.
In der Ausgabe in Codeblock 9-2 zeigt Zeile 17 der Aufrufhistorie auf die Zeile in unserem Projekt, die das Problem verursacht: Zeile 4 in src/main.rs. Wenn wir nicht wollen, dass unser Programm abbricht, sollten wir bei der ersten Zeile, die auf eine von uns geschriebenen Datei verweist, mit der Untersuchung beginnen. In Codeblock 9-1, wo wir absichtlich Code geschrieben haben, der das Programm abbricht, besteht die Möglichkeit das Problem zu beheben darin, kein Element außerhalb des Bereichs der Vektorindizes anzufordern. Wenn dein Code in Zukunft abbricht, musst du herausfinden, bei welcher Aktion der Code mit welchen Werten abbricht und was der Code stattdessen tun sollte.
In Abschnitt „Wann panic!
verwenden und wann
nicht?“ später in diesem Kapitel kommen wir noch
einmal auf panic!
zurück und wann wir panic!
verwenden sollten und wann
nicht, um Fehlerfälle zu behandeln. Als Nächstes schauen wir uns an, wie man
Fehler mit Result
abfangen kann.