Modul 7 — Fehlerbehandlung
Fehler typisieren: eigene Error-Enums
In Modul 5 kennengelernt: Result als Aufschrift "kann schiefgehen". Bisher war der Fehlerteil oft ein bloßer String — schnell, aber unstrukturiert. Ein eigenes Error-Enum macht jede Fehlerart zu einem benannten Wert, auf den Caller gezielt reagieren können:
#[derive(Debug, PartialEq)]
enum ParseError {
InvalidLine(String),
NotANumber(String),
}
fn parse_line(line: &str) -> Result<(String, u32), ParseError> {
let (key, value) = line
.split_once('=')
.ok_or_else(|| ParseError::InvalidLine(line.to_string()))?;
let num = value
.trim()
.parse::<u32>()
.map_err(|_| ParseError::NotANumber(line.to_string()))?;
Ok((key.trim().to_string(), num))
}
fn main() {
match parse_line("timeout=30") {
Ok((key, value)) => println!("{key} = {value}"),
Err(e) => println!("Fehler: {e:?}"),
}
}
Zwei Dinge passieren hier: ok_or_else verwandelt das Option von
split_once in ein Result mit unserer Fehlerart, und map_err übersetzt
den Fremdfehler des Parsers (ParseIntError) in unser Enum. Am Ende
hat die ganze Funktion genau einen Fehlertyp.
Warum kein String als Fehlertyp?
- Ein String kann alles behaupten — ein Enum hat fest definierte Varianten
- Der Compiler erzwingt: wer matcht, behandelt jede Fehlerart (Exhaustivität)
- Tests können Fehler exakt vergleichen (
assert_eq!mit PartialEq) - Der Kontext (die Zeile) reist als Datenfeld im Variant mit
📝 Schnellprüfung
Was gewinnt ein Aufrufer durch das Error-Enum gegenüber einem String-Fehler?
Übung: die zweite Variante
🧩 Übung m7-l1-e1
🔮 Vorhersage: Was passiert beim Ausführen?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Der ?-Operator: Fehler elegant weiterreichen
Funktionen, die selbst nichts Sinnvolles mit einem Fehler anfangen können, reichen ihn nach oben weiter. Mit match wird das eine Match-Pyramide — mit dem ?-Operator eine Zeile pro Schritt:
fn parse_number(s: &str) -> Result<i32, String> {
s.trim()
.parse::<i32>()
.map_err(|_| String::from("keine Zahl"))
}
fn parse_pair(s: &str) -> Result<(i32, i32), String> {
let (a, b) = s
.split_once(',')
.ok_or_else(|| String::from("Komma fehlt"))?;
let x = parse_number(a)?;
let y = parse_number(b)?;
Ok((x, y))
}
fn main() -> Result<(), String> {
let (x, y) = parse_pair("3,4")?;
println!("Paar: {x}, {y}");
Ok(())
}
Das ? bedeutet: Ok-Wert auspacken und weitermachen — oder den Fehler
sofort aus der Funktion zurückgeben. Und sogar main darf Result
zurückgeben: bei Err beendet sich das Programm mit einem Nicht-Null-Exitcode
und druckt den Fehler — genau das, was Skripte und CI von einem CLI-Tool
erwarten.
Die drei ?-Regeln
- Die umgebende Funktion muss selbst Result (oder Option) zurückgeben
- Unterschiedliche Fehlertypen?
?konvertiert automatisch per From — oder du übersetzt vorher mit map_err ?ist für Propagieren da; die Entscheidung (melden, retry, abbrechen) fällt ganz oben
Probiere es selbst: schreibe eine Funktion doppelt(eingabe: &str), die
die Eingabe als Zahl parst, verdoppelt und als Result zurückgibt — mit ?
statt match.
📝 Schnellprüfung
Was passiert, wenn parse_pair('7,x') mit ? läuft und parse_number('x') einen Fehler liefert?
Übung: die Match-Pyramide abbauen
🧩 Übung m7-l2-e1
🔮 Vorhersage: Was passiert beim Ausführen?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Panic oder Result?
Rust kennt zwei Fehlerwelten: Panic (unwideruflich: das Programm bricht ab) und Result (wiederherstellbar: der Caller entscheidet). Die Faust- regel der Praxis:
- Panic — nur für Bugs und gebrochene Invarianten: ein Index außerhalb des Vec, eine Konfiguration, die schon beim Start belegt, dass der Code falsch ist
- Result — für alles Erwartbare: fehlende Datei, kaputte Nutzereingabe, Timeout im Netzwerk
fn lade_konfig(pfad: &str) -> Result<String, String> {
match std::fs::read_to_string(pfad) {
Ok(inhalt) => Ok(inhalt),
Err(e) => Err(format!("Konfiguration nicht lesbar: {e}")),
}
}
fn main() {
match lade_konfig("app.conf") {
Ok(inhalt) => println!("{} Bytes geladen", inhalt.len()),
Err(msg) => {
eprintln!("Start abgebrochen: {msg}");
std::process::exit(1);
}
}
}
.expect("Kontext") ist der ehrlichere Bruder von .unwrap(): bei Panic
druckt er den Kontext mit — aber beides gehört nicht auf Pfade, deren
Scheitern du erwartest. Wenn es doch mal knallt, hilft:
RUST_BACKTRACE=1 ./meinprogramm
Der Backtrace zeigt die Aufrufliste im Panic-Moment — die Spur von der kaputten Stelle zurück zu dir.
📝 Schnellprüfung
Ein Nutzer gibt abc als Alter ein. Panic oder Result?
Übung: kein Panic mehr
🧩 Übung m7-l3-e1
🔮 Vorhersage: Was passiert beim Ausführen?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Mini-Projekt: Config-Parser
Alles aus dem Modul in einem Baustein: ein Parser für key=wert-Zeilen,
der Fehler typisiert meldet und nie paniziert. Das Szenario:
🏢 Praxis-Szenario: Die Server-Konfiguration
Dein Server liest seine Einstellungen aus einer Textdatei: timeout=30, retries=5. Ein Kollege editiert die Datei von Hand — und vertippt sich. Bisher: das Programm paniziert beim Start mitten in der Nacht, ohne Hinweis, welche Zeile schuld war. Dein Vorschlag: ein Parser, der jede Zeile in ein Ergebnis mit klarer Fehlerart übersetzt und kaputte Zeilen benennt, statt unterzugehen.
Die Übung baut genau diesen Parser — vom Enum bis zur Ausgabeschleife:
🧩 Übung m7-l4-e1
🔮 Vorhersage: Was passiert beim Ausführen?
Was du in Modul 7 geschafft hast
- Fehlerarten als Varianten — der Compiler zwingt zur vollständigen Behandlung
- ? macht Propagieren zur One-Liner; main als Result nutzt Exitcodes
- Panic ist die Ausnahme für Bugs — nicht die Norm für Fehler
- Ein Parser, der kaputte Eingaben benennt statt abzustürzen
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
Modul-Checkpoint
Checkpoint: Modul 7
Bestehensgrenze: 80 %. Der Versuch wird bewertet und zählt für das Modul-Gate.
Bereit, dein Wissen zu testen?
Probiere unsere Übungsprüfungen mit Hunderten von realistischen Fragen aus.
Üben starten →