English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Rust hat ein einzigartiges Mechanismus zur Verarbeitung von Ausnahmezuständen, der nicht so einfach ist wie der `try`-Mechanismus in anderen Sprachen.
Zunächst gibt es in der Regel zwei Arten von Fehlern in einem Programm: wiederaufbringliche Fehler und unwiederbringliche Fehler.
Ein typisches Beispiel für wiederaufbringliche Fehler ist ein Dateizugriffsfehler. Wenn ein Dateizugriff fehlschlägt, könnte dies daran liegen, dass die Datei bereits belegt ist, was normal ist, und wir können durch Warten eine Lösung finden.
Es gibt jedoch auch Fehler, die durch logische Fehler im Programmverlauf verursacht werden, die nicht durch das Programm gelöst werden können, z.B. den Zugriff auf einen Bereich außerhalb des Endes eines Arrays.
Die meisten Programmiersprachen unterscheiden nicht zwischen diesen beiden Arten von Fehlern und verwenden die Klasse `Exception` (Ausnahme) zur Darstellung von Fehlern. In Rust gibt es keine `Exception`.
Für wiederaufbringliche Fehler wird die Klasse `Result<T, E>` verwendet, und für unwiederbringliche Fehler wird das Macro `panic!` verwendet.
Bisher wurde in diesem Kapitel noch keine spezielle Einführung in die Syntax von Rust-Makros gegeben, aber das Macro `println!` wurde bereits verwendet, da diese Makros einfach zu verwenden sind, daher ist es vorerst nicht erforderlich, sie vollständig zu beherrschen. Wir können auf die gleiche Weise lernen, wie man das Macro `panic!` verwendet.
fn main() { panic!("error occurred"); println!("Hello, Rust"); }
运行结果:
thread 'main' panicked at 'error occurred', src\main.rs:3:5 Hinweis: Führen Sie mit `RUST_BACKTRACE=` aus.1`environment variable to display a backtrace.
Es ist offensichtlich, dass das Programm nicht wie geplant bis println!("Hello, Rust") ausgeführt wird, sondern stoppt, wenn das Macro `panic!` aufgerufen wird.
Unwiederbringliche Fehler führen sicherlich dazu, dass das Programm tödlich getroffen wird und abgestürzt wird.
Lassen Sie uns die beiden Zeilen der Fehlerausgabe betrachten:
Die erste Zeile gibt den Ort des Aufrufs des Macro `panic!` und die ausgegebenen Fehlerinformationen an.
Die zweite Zeile ist ein Hinweis, der ins Chinesische übersetzt "Durch `RUST_BACKTRACE=` angezeigt wird.1`Um die Umgebungsvariablen auszuführen und die Rückverfolgung anzuzeigen". In den nächsten Abschnitten werden wir die Rückverfolgung (backtrace) vorstellen.
Anschließend zum vorherigen Beispiel, erstellen wir einen neuen Terminal in VSCode:
Setze die Umgebungsvariablen im neu erstellten Terminal (die Methoden zur Erstellung der Umgebungsvariablen sind unterschiedlich, hier werden zwei Hauptmethoden vorgestellt):
Wenn du Windows verwendest 7 In Windows-Systemversionen höher als die folgenden wird der Standard-Terminalkommandozeile Powershell sein. Verwende folgenden Befehl:
$env:RUST_BACKTRACE=1 ; cargo run
Wenn du ein Linux- oder macOS-ähnliches UNIX-System verwendest, wird in der Regel bash als Standardkommandozeile verwendet. Verwende folgenden Befehl:
RUST_BACKTRACE=1 cargo run
Dann siehst du folgenden Text:
thread 'main' panicked at 'error occurred', src\main.rs:3:5 stack backtrace: ... 11: greeting::main at ".\src\main.rs":3 ...
Ein weiterer Umgang mit un wiederaufnehmbarer Fehler ist das Rückschreiten, das den Ausführungsstack ausrollt und alle Informationen ausgibt, bevor das Programm abbricht. Die folgenden Punkte sind viele ausgeblendetene Informationen, und wir können den Fehler finden, der durch das Makro panic! ausgelöst wurde.
Dieser Begriff ähnelt stark dem Konzept von Ausnahmen in der Programmiersprache Java. Tatsächlich setzen wir in der Programmiersprache C oft die Rückgabewerte von Funktionen auf Integer, um Fehler auszudrücken, die von einer Funktion aufgetreten sind, in Rust verwenden wir die Enumeration Result<T, E> als Rückgabewert, um Ausnahmen auszudrücken:
enum Result<T, E> { Ok(T), Err(E), }
Die Rückgabewerte von Funktionen, die in der Rust-Bibliothek Ausnahmen verursachen können, sind alle vom Typ Result. Zum Beispiel: Wenn wir versuchen, eine Datei zu öffnen:
use std::fs::File; fn main() { let f = File::open("hello.txt"); match f { Ok(file) => { println!("Datei erfolgreich geöffnet."); }, Err(err) => { println!("Datei konnte nicht geöffnet werden."); } } }
Wenn die Datei hello.txt nicht existiert, wird der folgende Text ausgegeben: "Datei konnte nicht geöffnet werden."。
Natürlich kann die if let-Syntax, die wir im Kapitel über Enumerationen besprochen haben, die match-Syntax vereinfachen:
use std::fs::File; fn main() { let f = File::open("hello.txt"); if let Ok(file) = f { println!("Datei erfolgreich geöffnet."); } else { println!("Datei konnte nicht geöffnet werden."); } }
Um einen recoverbaren Fehler als unrecoverbaren Fehler zu behandeln, bietet der Result-Typ zwei Methoden: unwrap() und expect(message: &str) :
use std::fs::File; fn main() { let f1 = File::open("hello.txt").unwrap(); let f2 = File::open("hello.txt").expect("Failed to open."); }
Dieser Codeblock entspricht dem Aufruf der Macro panic! im Falle eines Err in Result. Der Unterschied liegt darin, dass expect eine spezifische Fehlermeldung an panic! senden kann.
Bisher haben wir die Fehlerbehandlung beim Empfang von Fehlern besprochen, aber was tun, wenn wir eine eigene Funktion schreiben möchten, die Fehler weiterleiten soll?
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn main() { let r = f(10000); if let Ok(v) = r { println!("Ok: f(-1) = {}", v); } else { println!("Err"); } }
运行结果:
Ok: f(-1) = {}", v); 10000
Dieser Codeblock enthält die Funktion f, die den Fehler verursacht, schreiben wir eine Fehlerweiterleitungs-Funktion g:
fn g(i: i32) -> Result<i32, bool> { let t = f(i); return match t { Ok(i) => Ok(i), Err(b) => Err(b) }; }
Die Funktion g leitet mögliche Fehler der Funktion f weiter (hier ist g nur ein einfaches Beispiel, normalerweise enthalten die Fehlerweiterleitungs-Functions viele andere Operationen).
So ist das etwas langweilig, in Rust kann man nach dem Result-Objekt den ?-Operator hinzufügen, um ähnliche Err direkt weiterzuleiten:
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn g(i: i32) -> Result<i32, bool> { let t = f(i)?; Ok(t) // Da t bestimmt nicht Err ist, ist t hier bereits i32 Typ } fn main() { let r = g(10000); if let Ok(v) = r { println!("Ok: g(10000) = {}", v); } else { println!("Err"); } }
运行结果:
Ok: g(10000) = {} 10000
Das ?-Zeichen hat die Funktion, den Wert von Result<T, E> direkt abzurufen, falls ein Fehler auftritt, wird der fehlerhafte Result zurückgegeben. Daher wird das ?-Zeichen nur in Funktionen verwendet, deren Rückgabetyp Result<T, E> ist, wobei der Typ E mit dem Typ des von ? behandelten Errors übereinstimmen muss.
到目前为止,Rust 似乎没有像 try 块一样可以令任何位置发生的同类异常都直接得到相同的解决的语法,但这样并不意味着 Rust 实现不了:我们完全可以把 try 块在独立的函数中实现,将所有的异常都传递出去解决。实际上这才是一个良好的程序应当遵循的编程方法:应该注重独立功能的完整性。
但是这样需要判断 Result 的 Err 类型,获取 Err 类型的函数是 kind()。
use std::io; use std::io::Read; use std::fs::File; fn read_text_from_file(path: &str) -> Result<String, io::Error> { let mut f = File::open(path)?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } fn main() { let str_file = read_text_from_file("hello.txt"); match str_file { Ok(s) => println!("{}", s), Err(e) => { match e.kind() { io::ErrorKind::NotFound => { println!("没有这样的文件"); }, _ => { println!("无法读取文件"); } } } } }
运行结果:
没有这样的文件