Rust hat keine Ausnahmen. Fehler sind Werte vom Typ Result<T, E>:
enum Result<T, E> {
Ok(T), // Erfolg mit Wert
Err(E), // Fehler mit Beschreibung
}use std::num::ParseIntError;
fn parse_alter(text: &str) -> Result<u32, ParseIntError> {
text.trim().parse::<u32>()
}
fn teile(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err("Division durch 0".to_string()) } else { Ok(a / b) }
}
fn main() {
match parse_alter(" 17 ") {
Ok(alter) => println!("Alter: {alter}"),
Err(e) => println!("Fehler: {e}"),
}
match parse_alter("abc") {
Ok(alter) => println!("Alter: {alter}"),
Err(e) => println!("Fehler: {e}"),
}
println!("{:?} {:?}", teile(10.0, 4.0), teile(1.0, 0.0));
println!("{}", teile(8.0, 2.0).unwrap_or(0.0));
println!("{}", "42".parse::<i32>().map(|n| n + 1).unwrap_or(-1));
}Alter: 17
Fehler: invalid digit found in string
Ok(2.5) Err("Division durch 0")
4
43Der Compiler warnt, wenn du ein Result ignorierst (#[must_use]). Fehler gehen nicht unter.
Der ?-Operator
Der Operator ? gibt bei einem Fehler sofort den Fehler an den Aufrufer weiter, bei Erfolg liefert er den Wert:
use std::fs;
use std::io;
use std::num::ParseIntError;
fn lese_zahl(pfad: &str) -> Result<i32, Box<dyn std::error::Error>> {
let text = fs::read_to_string(pfad)?; // io::Error wird weitergereicht
let n: i32 = text.trim().parse()?; // ParseIntError ebenfalls
Ok(n * 2)
}
fn addiere(a: &str, b: &str) -> Result<i32, ParseIntError> {
Ok(a.parse::<i32>()? + b.parse::<i32>()?)
}
fn main() {
println!("{:?}", addiere("2", "3"));
println!("{:?}", addiere("2", "x").is_err());
match lese_zahl("gibt-es-nicht.txt") {
Ok(n) => println!("{n}"),
Err(e) => println!("Fehler: {e}"),
}
let _ = io::Error::other("Beispiel");
}Ok(5) true Fehler: No such file or directory (os error 2)
Box<dyn Error> nimmt jeden Fehlertyp auf und ist die bequeme Wahl für Programme. Bibliotheken definieren dagegen eigene Fehlertypen.
Eigene Fehlertypen
use std::fmt;
#[derive(Debug)]
enum KontoFehler {
ZuWenigGuthaben { fehlt: f64 },
UngueltigerBetrag(f64),
}
impl fmt::Display for KontoFehler {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KontoFehler::ZuWenigGuthaben { fehlt } => write!(f, "Es fehlen {fehlt:.2} Euro"),
KontoFehler::UngueltigerBetrag(b) => write!(f, "Ungültiger Betrag: {b}"),
}
}
}
impl std::error::Error for KontoFehler {}
struct Konto { stand: f64 }
impl Konto {
fn abheben(&mut self, betrag: f64) -> Result<f64, KontoFehler> {
if betrag <= 0.0 { return Err(KontoFehler::UngueltigerBetrag(betrag)); }
if betrag > self.stand { return Err(KontoFehler::ZuWenigGuthaben { fehlt: betrag - self.stand }); }
self.stand -= betrag;
Ok(self.stand)
}
}
fn main() {
let mut k = Konto { stand: 100.0 };
for b in [30.0, 100.0, -5.0] {
match k.abheben(b) {
Ok(rest) => println!("ok, Rest {rest}"),
Err(e) => println!("Fehler: {e}"),
}
}
}ok, Rest 70 Fehler: Es fehlen 30.00 Euro Fehler: Ungültiger Betrag: -5
Die Bibliotheken thiserror (für Fehlertypen in Bibliotheken) und anyhow (für Programme) nehmen dir dieses Gerüst ab.
Fehler umwandeln
use std::num::ParseIntError;
#[derive(Debug)]
enum EingabeFehler {
Leer,
Zahl(ParseIntError),
}
impl From<ParseIntError> for EingabeFehler { // damit ? automatisch umwandelt
fn from(e: ParseIntError) -> Self { EingabeFehler::Zahl(e) }
}
fn lies(text: &str) -> Result<i32, EingabeFehler> {
if text.is_empty() { return Err(EingabeFehler::Leer); }
Ok(text.parse::<i32>()?)
}
fn main() {
println!("{:?}", lies("12"));
println!("{:?}", lies(""));
println!("{:?}", lies("x").map_err(|e| format!("{e:?}")));
let liste: Result<Vec<i32>, _> = ["1", "2", "3"].iter().map(|s| s.parse::<i32>()).collect();
println!("{liste:?}");
let kaputt: Result<Vec<i32>, _> = ["1", "x"].iter().map(|s| s.parse::<i32>()).collect();
println!("{}", kaputt.is_err());
}Ok(12)
Err(Leer)
Err("Zahl(ParseIntError { kind: InvalidDigit })")
Ok([1, 2, 3])
truecollect() kann eine Liste von Results in ein einzelnes Result<Vec<_>, _> verwandeln (bricht beim ersten Fehler ab).
panic!, unwrap und expect
panic! beendet das Programm (mit Meldung, optional Stacktrace). Das ist für Programmierfehler gedacht, nicht für erwartbare Probleme:
fn main() {
let ergebnis = std::panic::catch_unwind(|| {
let v = vec![1, 2, 3];
v[10] // Index außerhalb: Panic
});
println!("{}", ergebnis.is_err());
let n: Option<i32> = None;
let r = std::panic::catch_unwind(|| n.expect("Wert sollte da sein"));
println!("{}", r.is_err());
}true true
(Beim echten Ausführen siehst du zusätzlich Panic-Meldungen auf dem Fehlerkanal; hier ist nur die normale Ausgabe gezeigt.)
Regeln: In Bibliotheken Result zurückgeben. unwrap()/expect() nur, wenn der Fehler logisch unmöglich ist, in Tests oder in schnellen Prototypen. Benutze expect("Grund"), damit die Meldung hilft.
Merke
Result<T, E>hatOk(T)undErr(E); Fehler sind Werte, keine Ausnahmen?reicht Fehler weiter und wandelt sie überFromumBox<dyn Error>in Programmen, eigene Fehler-Enums in Bibliotheken (oderthiserror/anyhow)map,map_err,unwrap_or,and_thenverarbeiten Ergebnissepanic!undunwrapnur bei Programmierfehlern und unmöglichen Fällen
Aufgabe
Schreibe lese_summe(text: &str) -> Result<i32, ParseIntError>, die kommagetrennte Zahlen summiert und bei der ersten ungültigen Zahl einen Fehler liefert.