Einleitung: Die Philosophie der Fehlerbehandlung in Rust
Im Gegensatz zu vielen Sprachen mit Ausnahmen (Java, Python, C++) verwendet Rust kein klassisches try/catch-Konstrukt. Stattdessen bietet die Sprache einen expliziteren und typsicheren Ansatz. Fehler in Rust sind keine versteckten Kontrollflüsse, sondern Rückgabewerte. Dies macht den Code vorhersagbar und schützt vor "stillen" Fehlern.
Rust hat zwei Haupttypen zur Darstellung von Fehlersituationen:
- Result<T, E> — für behebbare Fehler (I/O-Fehler, Parsing-Fehler, Geschäftslogik-Fehler).
- Option<T> — für das Fehlen eines Wertes (ein Analogon zu null, aber sicher).
In diesem Artikel werden wir untersuchen, wie man mit diesen Typen arbeitet, womit man sie aus der Welt von try/catch ersetzt und wie man zuverlässigen Code in Rust schreibt.
1. Result<T, E>: Das Hauptwerkzeug für die Fehlerbehandlung
Der Result-Typ ist ein Enum mit zwei Varianten: Ok(T) (Erfolg) und Err(E) (Fehler). Er wird in den meisten Funktionen verwendet, die fehlschlagen könnten.
1.1. Erstellen und Zurückgeben eines Result
use std::fs::File;use std::io::Read;
fn read_file(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; // der ?-Operator — Analogon zu try! let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents)}Hier ist der ?-Operator ein Schlüsselelement. Er funktioniert wie try in anderen Sprachen: Wenn das Ergebnis Err ist, gibt die Funktion diesen Fehler sofort zurück. Wenn Ok, extrahiert er den Wert.
1.2. Behandlung mit match
Die expliziteste Methode ist die Verwendung von match:
let result = read_file("config.txt");
match result { Ok(content) => println!("File read: {}", content), Err(e) => eprintln!("Error: {}", e),}Dies ist ein Analogon zu try/catch, aber flexibler und kontrollierter.
1.3. Hilfsmethoden
.unwrap()— führt bei Fehler zu einem Panik (nur für Prototypen)..expect(msg)— führt mit einer benutzerdefinierten Nachricht zu einem Panik..unwrap_or(default)— gibt einen Standardwert zurück..is_ok()/.is_err()— Zustandsprüfung.
2. Option<T>: Sichere Behandlung fehlender Werte
Option ist ein weiteres Enum mit den Varianten Some(T) und None. Es ersetzt null und nullptr und eliminiert Fehler durch Nullzeiger-Dereferenzierung.
2.1. Anwendungsbeispiel
fn find_user(id: u32) -> Option<String> { if id == 1 { Some("Alice".to_string()) } else { None }}
let user = find_user(2);println!("User: {}", user.unwrap_or("Guest".to_string()));2.2. Kombinieren mit Result
Oft wird Option zur einheitlichen Behandlung in Result umgewandelt:
fn get_user_name(id: u32) -> Result<String, String> { find_user(id).ok_or("User not found".to_string())}Die Methode .ok_or() verwandelt Some in Ok und None in Err.
3. Der ?-Operator: Ein eleganter Ersatz für try/catch
Der ?-Operator ist syntaktischer Zucker, der automatisch Result oder Option entpackt. Er ist nur in Funktionen verfügbar, die den entsprechenden Typ zurückgeben.
3.1. Beispiel mit einer Aufrufkette
use std::num::ParseIntError;
fn parse_and_add(a: &str, b: &str) -> Result<i32, ParseIntError> { let x: i32 = a.parse()?; let y: i32 = b.parse()?; Ok(x