Result in Rust
Option steht für einen Wert, der fehlen kann. Result steht für eine Operation, die fehlschlagen kann. Anders als bei einem fehlenden Wert gibt es bei einem Fehlschlag meistens auch einen Grund dafür.
So sieht Result aus
Abschnitt betitelt „So sieht Result aus“Result ist ein weiteres Enum aus der Standardbibliothek. Diesmal tragen beide Varianten Daten:
enum Result<T, E> { Ok(T), // Erfolg, mit einem Wert Err(E), // Fehlschlag, mit einem Fehler}Eine Funktion, die fehlschlagen kann
Abschnitt betitelt „Eine Funktion, die fehlschlagen kann“.parse() kennst du schon von der Seite Typumwandlung. Das ist ein gutes Beispiel aus der Praxis: Text in eine Zahl umzuwandeln, klappt nicht, wenn der Text gar keine Zahl ist.
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}Ausgabe:
Ok(42)Err(ParseIntError { kind: InvalidDigit })InvalidDigit bedeutet „ungültige Ziffer“: In "abc" steckt nichts, was sich als Zahl lesen lässt.
Ein Result mit match behandeln
Abschnitt betitelt „Ein Result mit match behandeln“fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("Umgewandelt: {number}"), Err(e) => println!("Umwandlung fehlgeschlagen: {e}"), }}Das ist dieselbe Form wie bei einer Option mit match. Nur liefert dir der Err-Zweig einen echten Fehlerwert, den du untersuchen oder melden kannst – nicht bloß „hier war nichts“.
Eine eigene Funktion, die Result zurückgibt
Abschnitt betitelt „Eine eigene Funktion, die Result zurückgibt“fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("Division durch null ist nicht möglich")) } else { Ok(a / b) }}
fn main() { match divide(10.0, 2.0) { Ok(result) => println!("Ergebnis: {result}"), Err(e) => println!("Fehler: {e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("Ergebnis: {result}"), Err(e) => println!("Fehler: {e}"), }}Ausgabe:
Ergebnis: 5Fehler: Division durch null ist nicht möglichWeil die Funktion Result<f64, String> zurückgibt, muss sich jeder Aufrufer mit einem möglichen Fehlschlag beschäftigen. Eine Division durch null kann so nie versehentlich als normale Zahl weiterverwendet werden.
Der ?-Operator
Abschnitt betitelt „Der ?-Operator“Wenn du mehrere Operationen hintereinander ausführst, die jeweils fehlschlagen können, landest du schnell bei tief verschachtelten match-Blöcken. Der ?-Operator macht das flach. Er bedeutet: „Ist das ein Err, gib diesen Fehler sofort aus der aktuellen Funktion zurück. Sonst gib mir den Wert aus dem Ok.“
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // kehrt sofort zurück, wenn die Umwandlung fehlschlägt Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("hoppla"));}Ausgabe:
Ok(42)Err(ParseIntError { kind: InvalidDigit })Ohne ? bräuchte diese Zeile ein eigenes match, nur um number auszupacken oder mit dem Fehler vorzeitig abzubrechen. ? erledigt genau das mit einem einzigen Zeichen. Es funktioniert allerdings nur in einer Funktion, die selbst ein Result (oder eine Option) zurückgibt. Nur dann gibt es einen Ort, an den der Fehler zurückgegeben werden kann.
Result oder panic!?
Abschnitt betitelt „Result oder panic!?“Rust hat auch panic!. Das hält das Programm sofort an – mehr dazu auf der nächsten Seite. Als Faustregel gilt: Nimm Result für Fehlschläge, die ein Aufrufer sinnvollerweise behandeln möchte (falsche Eingaben, eine fehlende Datei). Heb dir panic! für Situationen auf, die auf einen echten Bug hinweisen und in denen Weitermachen schlimmer wäre als Anhalten.
Sobald deine Funktionen auf mehrere verschiedene Arten fehlschlagen können, reicht ein einfacher String als Fehler nicht mehr aus. Auf der Seite Eigene Fehlertypen lernst du, wie du deinen eigenen Fehlertyp definierst. Und anyhow und thiserror zeigt dir die Crates, mit denen die meisten Rust-Projekte sich den Boilerplate-Code sparen.
Übungen
Abschnitt betitelt „Übungen“1. Ein Alter prüfen
Abschnitt betitelt „1. Ein Alter prüfen“Schreib fn parse_age(input: &str) -> Result<u32, String>. Die Funktion soll ein Err mit einer hilfreichen Meldung zurückgeben, wenn der Text keine Zahl ist oder wenn die Zahl größer als 150 ist. Teste sie mit "34", "abc" und "200". Tipp: Reagiere mit match auf input.parse::<u32>(), und prüf im Ok-Zweig mit einem if die Größe.
Lösung anzeigen
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age} ist zu alt, um echt zu sein")) } else { Ok(age) } } Err(_) => Err(format!("'{input}' ist keine Zahl")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("Alter: {age}"), Err(e) => println!("Fehler: {e}"), } }}Ausgabe:
Alter: 34Fehler: 'abc' ist keine ZahlFehler: 200 ist zu alt, um echt zu sein2. Zwei Zahlen addieren mit ?
Abschnitt betitelt „2. Zwei Zahlen addieren mit ?“Schreib fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError>. Die Funktion wandelt beide Strings in Zahlen um und gibt ihre Summe zurück. Benutze ? statt match. Gib das Ergebnis für ("10", "20") und für ("10", "zwanzig") aus. Tipp: Du brauchst ein ? pro parse, und die letzte Zeile packt die Summe in Ok(...).
Lösung anzeigen
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "zwanzig"));}Ausgabe:
Ok(30)Err(ParseIntError { kind: InvalidDigit })Zusammenfassung
Abschnitt betitelt „Zusammenfassung“Result<T, E>steht für eine Operation, die gelingen (Ok) oder mit einem Fehlerwert fehlschlagen (Err) kann.matchbehandelt beide Fälle ausdrücklich – nach demselben Muster wie beiOption.- Der
?-Operator gibt einErrautomatisch vorzeitig zurück. So brauchst du nicht nach jedem Aufruf, der fehlschlagen kann, einmatch. - Nimm lieber
Resultalspanic!für Fehler, von denen sich ein Aufrufer sinnvoll erholen kann.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Welche zwei Varianten hat Result<T, E>?
2. Was macht der ?-Operator in einer Funktion, die ein Result zurückgibt?
3. Wann solltest du laut dieser Seite Result statt panic! nehmen?
4. Was gibt divide(10.0, 0.0) im Beispiel auf dieser Seite zurück?
5. Warum funktioniert der ?-Operator nur in einer Funktion, die selbst ein Result (oder eine Option) zurückgibt?
Punkte: 0 / 5
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Behebbare Fehler mit Result – Das Rust-Buch: das offizielle Kapitel zu
Result,unwrap,expectund dem?-Operator, mit Beispielen zum Öffnen von Dateien. std::result– Doku der Standardbibliothek (auf Englisch): ein Überblick über die Arbeit mitResult, mit Methoden wiemap_err,unwrap_orundok.- Result – Rust by Example (auf Englisch): ausführbare Beispiele, wie du ein
Resultbehandelst und ausmainzurückgibst. - The ? operator – Rust by Example (auf Englisch): ein kurzer, gezielter Blick darauf, wie
?verschachteltematch-Blöcke ersetzt.