GGistDev

Error Handling in Rust

Use Result<T, E> for recoverable errors and panic! for unrecoverable bugs. The ? operator propagates errors succinctly.

Result and ?

use std::fs::File; use std::io::{self, Read};

fn read_all(path: &str) -> io::Result<String> {
    let mut s = String::new();
    File::open(path)?.read_to_string(&mut s)?;
    Ok(s)
}

? converts the error into the function’s return type via From/Into or From implementations.

Defining error types

Use enums to represent domain errors; derive Debug/thiserror::Error for ergonomics.

use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
    #[error("io: {0}")] Io(#[from] std::io::Error),
    #[error("parse: {0}")] ParseInt(#[from] std::num::ParseIntError),
}

Anyhow for apps

anyhow::Result<T> provides easy error aggregation for applications (not libraries).

fn run() -> anyhow::Result<()> {
    let n: i32 = std::env::var("N")?.parse()?;
    println!("{n}");
    Ok(())
}

panics

Use panic! for bugs (violated invariants). Catch panics in tests; avoid panicking in libraries where callers can recover.

Logging and context

Attach context for easier debugging.

use anyhow::Context;
File::open(path).with_context(|| format!("opening {path}"))?;

Summary

  • Prefer Result with ? for recoverable errors
  • Define error enums (thiserror) for libraries; use anyhow for apps
  • Add context; reserve panic! for bugs/invariants