EMZETT.
Login

Kurz: Rust hat keine Ausnahmen. Fehler sind Werte vom Typ Result<T, E>:

Teil des Kurses Rust

Kapitel 7 von 13 im Kurs Rust. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

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));
}

Ausgabe:

Alter: 17
Fehler: invalid digit found in string
Ok(2.5) Err("Division durch 0")
4
43

Der 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");
}

Ausgabe:

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}"),
        }
    }
}

Ausgabe:

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());
}

Ausgabe:

Ok(12)
Err(Leer)
Err("Zahl(ParseIntError { kind: InvalidDigit })")
Ok([1, 2, 3])
true

collect() 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());
}

Ausgabe:

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> hat Ok(T) und Err(E); Fehler sind Werte, keine Ausnahmen
  • ? reicht Fehler weiter und wandelt sie über From um
  • Box<dyn Error> in Programmen, eigene Fehler-Enums in Bibliotheken (oder thiserror/anyhow)
  • map, map_err, unwrap_or, and_then verarbeiten Ergebnisse
  • panic! und unwrap nur bei Programmierfehlern und unmöglichen Fällen

Übungsaufgabe

Schreibe lese_summe(text: &str) -> Result<i32, ParseIntError>, die kommagetrennte Zahlen summiert und bei der ersten ungültigen Zahl einen Fehler liefert.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Structs, Enums und Option

Weiter: Vec, HashMap und andere Collections

Alle Kapitel: Rust im Überblick