Rust Error Handling in 2026: Result, Option, thiserror en anyhow in de Praktijk

Complete gids voor foutafhandeling in Rust met Result, Option, de ?-operator en de crates thiserror en anyhow voor robuuste en onderhoudbare applicaties.

Rust Error Handling in 2026: Result, Option, thiserror en anyhow in de Praktijk

Foutafhandeling in Rust draait om twee enums—Result<T, E> en Option<T>—die expliciete afhandeling van succes, falen en afwezigheid afdwingen tijdens compilatie. Anders dan exceptions die onzichtbaar propageren, maakt de Rust-aanpak foutpaden zichtbaar in functiesignaturen en elimineert zo complete categorieën van runtime-verrassingen. De ?-operator, gecombineerd met de crates thiserror en anyhow, stroomlijnt dit expliciete model zonder aan duidelijkheid in te boeten.

Wanneer Welk Type Gebruiken

Option<T> is geschikt voor waarden die legitiem afwezig kunnen zijn (configuratievelden, zoekresultaten). Result<T, E> wordt gebruikt wanneer operaties kunnen falen met betekenisvolle foutinformatie (bestands-I/O, netwerkverzoeken, parsing).

Basisprincipes van Result en Option

Result<T, E> vertegenwoordigt succes (Ok(T)) of falen (Err(E)). Option<T> vertegenwoordigt een waarde (Some(T)) of afwezigheid (None). Beide zijn somtypes—de compiler zorgt ervoor dat elke variant wordt afgehandeld.

error_examples.rsrust
// Demonstrates Result and Option basic patterns

fn find_user(id: u64) -> Option<String> {
    // Returns None if user doesn't exist
    if id == 0 {
        None
    } else {
        Some(format!("User-{}", id))
    }
}

fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
    // Returns Err if parsing fails
    s.parse::<u16>()
}

fn main() {
    // Option handling - must address None case
    match find_user(42) {
        Some(name) => println!("Found: {}", name),
        None => println!("User not found"),
    }

    // Result handling - must address Err case
    match parse_port("8080") {
        Ok(port) => println!("Port: {}", port),
        Err(e) => println!("Invalid port: {}", e),
    }
}

De compiler weigert code die deze returnwaarden negeert zonder expliciete afhandeling. Dit ontwerp vangt bugs op tijdens compilatie die in andere talen zouden verschijnen als null pointer exceptions of niet-afgevangen fouten.

De Vraagteken-Operator voor Beknopte Propagatie

De ?-operator transformeert uitgebreide match-ketens naar leesbare lineaire code. Bij toepassing op een Result keert deze vroegtijdig terug met de fout indien aanwezig, of pakt de succeswaarde uit. Hetzelfde geldt voor Option.

file_reader.rsrust
// Using ? for clean error propagation

use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn read_first_line(path: &str) -> Result<String, io::Error> {
    let file = File::open(path)?;  // Returns early if open fails
    let mut reader = BufReader::new(file);
    let mut line = String::new();
    reader.read_line(&mut line)?;  // Returns early if read fails
    Ok(line.trim().to_string())
}

fn get_port_from_config(path: &str) -> Result<u16, Box<dyn std::error::Error>> {
    let content = read_first_line(path)?;
    let port = content.parse::<u16>()?;  // ParseIntError converts via From
    Ok(port)
}

De ?-operator vereist dat het fouttype converteerbaar is naar het returnfouttype van de functie via de From-trait. Het gebruik van Box<dyn std::error::Error> zoals hierboven getoond accepteert elk fouttype dat de standaard Error trait implementeert.

Aangepaste Fouten Maken met thiserror

De thiserror crate elimineert boilerplate voor aangepaste fouttypes. Het leidt Error-, Display- en From-implementaties af via een procedurele macro.

errors.rsrust
// Custom error types using thiserror 2.0

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
    #[error("configuration file not found at {path}")]
    NotFound { path: String },

    #[error("invalid port number: {0}")]
    InvalidPort(#[from] std::num::ParseIntError),

    #[error("IO error reading config")]
    IoError(#[from] std::io::Error),

    #[error("missing required field: {0}")]
    MissingField(String),
}

fn load_config(path: &str) -> Result<Config, ConfigError> {
    let content = std::fs::read_to_string(path)?;  // IoError auto-converts
    let port: u16 = content
        .lines()
        .find(|l| l.starts_with("port="))
        .ok_or(ConfigError::MissingField("port".into()))?
        .strip_prefix("port=")
        .unwrap()
        .parse()?;  // ParseIntError auto-converts to InvalidPort

    Ok(Config { port })
}

struct Config {
    port: u16,
}

Het #[from]-attribuut genereert automatische conversies, waardoor naadloos gebruik van ? met verschillende onderliggende fouttypes mogelijk is. Foutmeldingen worden zelfdocumenterend door de #[error(...)] formatstrings.

Applicatie-Niveau Fouten met anyhow

Terwijl thiserror geschikt is voor bibliotheekcode met specifieke fouttypes, richt anyhow zich op applicaties waar foutcontext belangrijker is dan typegranulariteit. De Context-trait voegt beschrijvende berichten toe aan elke fout.

main.rsrust
// Application error handling with anyhow 1.0

use anyhow::{Context, Result, bail, ensure};

fn load_database_url() -> Result<String> {
    std::env::var("DATABASE_URL")
        .context("DATABASE_URL environment variable not set")
}

fn connect_to_database(url: &str) -> Result<DatabaseConnection> {
    ensure!(!url.is_empty(), "database URL cannot be empty");

    let conn = DatabaseConnection::new(url)
        .context("failed to establish database connection")?;

    if !conn.is_healthy() {
        bail!("database connection unhealthy after establishment");
    }

    Ok(conn)
}

fn main() -> Result<()> {
    let url = load_database_url()?;
    let conn = connect_to_database(&url)
        .context("application startup failed")?;

    // Context chains create readable error traces:
    // Error: application startup failed
    // Caused by:
    //     0: failed to establish database connection
    //     1: connection refused

    Ok(())
}

struct DatabaseConnection;
impl DatabaseConnection {
    fn new(_url: &str) -> Result<Self> { Ok(Self) }
    fn is_healthy(&self) -> bool { true }
}

De context()-methode omhult fouten met aanvullende informatie en creëert een keten die helpt bij debugging. De bail!-macro biedt een vroegtijdige exit met een geformatteerde foutmelding, terwijl ensure! fungeert als een assertie die een fout retourneert in plaats van te panicken.

Klaar om je Rust gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Combineren van thiserror en anyhow in Echte Projecten

Bibliotheken exposeren gestructureerde fouten via thiserror voor programmatische afhandeling door consumenten. Applicaties omhullen deze fouten met anyhow voor leesbare output. Deze scheiding houdt API's schoon terwijl debugbaarheid behouden blijft.

lib.rs - Library code with thiserrorrust
// Exposes typed errors for programmatic handling

use thiserror::Error;

#[derive(Error, Debug)]
pub enum PaymentError {
    #[error("insufficient funds: required {required}, available {available}")]
    InsufficientFunds { required: u64, available: u64 },

    #[error("card declined: {reason}")]
    CardDeclined { reason: String },

    #[error("payment provider unavailable")]
    ProviderUnavailable(#[source] reqwest::Error),
}

pub fn process_payment(amount: u64) -> Result<Receipt, PaymentError> {
    // Library returns specific, matchable error types
    Err(PaymentError::InsufficientFunds {
        required: amount,
        available: 50,
    })
}

pub struct Receipt;
main.rs - Application code with anyhowrust
// Wraps library errors with context

use anyhow::{Context, Result};
use my_payment_lib::{process_payment, PaymentError};

fn checkout(cart_total: u64) -> Result<()> {
    match process_payment(cart_total) {
        Ok(_receipt) => Ok(()),
        Err(PaymentError::InsufficientFunds { required, available }) => {
            // Handle specific case differently
            println!("Add {} to your balance", required - available);
            Ok(())
        }
        Err(e) => Err(e).context("checkout payment processing failed"),
    }
}

Dit patroon stelt aanroepers in staat om te matchen op specifieke varianten wanneer herstel mogelijk is, terwijl ze toch profiteren van rijke foutcontext bij het propageren van fouten naar boven. De Rust-community heeft dit grotendeels als standaard aangenomen, zoals besproken in de Rust API-richtlijnen.

Foutafhandelingspatronen voor Asynchrone Code

Async-functies retourneren Result net als synchrone functies. De ?-operator werkt identiek binnen async-blokken, en zowel thiserror als anyhow integreren zonder aanpassingen.

async_errors.rsrust
// Error handling in async Rust with Tokio

use anyhow::{Context, Result};
use std::time::Duration;

async fn fetch_user_data(user_id: u64) -> Result<UserData> {
    let response = reqwest::get(format!("https://api.example.com/users/{}", user_id))
        .await
        .context("HTTP request to user API failed")?;

    let status = response.status();
    if !status.is_success() {
        anyhow::bail!("user API returned status {}", status);
    }

    let data: UserData = response
        .json()
        .await
        .context("failed to parse user data JSON")?;

    Ok(data)
}

async fn fetch_with_retry(user_id: u64, attempts: u32) -> Result<UserData> {
    let mut last_error = None;

    for attempt in 1..=attempts {
        match fetch_user_data(user_id).await {
            Ok(data) => return Ok(data),
            Err(e) => {
                last_error = Some(e);
                if attempt < attempts {
                    tokio::time::sleep(Duration::from_millis(100 * attempt as u64)).await;
                }
            }
        }
    }

    Err(last_error.unwrap()).context(format!("failed after {} attempts", attempts))
}

#[derive(serde::Deserialize)]
struct UserData {
    name: String,
}

Bij het combineren van meerdere asynchrone operaties wordt try_join! uit tokio of futures aanbevolen om ze gelijktijdig uit te voeren terwijl de eerste fout wordt gepropageerd. Voor gerelateerde concepten biedt de module async/await interviewvragen verdere inzichten.

Downcasting en Foutinspectie

Zowel anyhow::Error als Box<dyn Error> ondersteunen downcasting om het oorspronkelijke fouttype te herstellen. Dit maakt het mogelijk om specifieke details te loggen terwijl generieke fouten worden gepropageerd.

downcasting.rsrust
// Inspecting wrapped error types

use anyhow::{Context, Result};
use std::io;

fn log_and_propagate(result: Result<()>) -> Result<()> {
    if let Err(ref e) = result {
        // Check if the root cause is a specific type
        if let Some(io_err) = e.downcast_ref::<io::Error>() {
            match io_err.kind() {
                io::ErrorKind::NotFound => {
                    tracing::warn!("file not found, using defaults");
                }
                io::ErrorKind::PermissionDenied => {
                    tracing::error!("permission denied - check file ownership");
                }
                _ => {
                    tracing::error!("IO error: {:?}", io_err);
                }
            }
        }
    }
    result
}

Downcasting overbrugt de kloof tussen generieke foutafhandeling en specifieke herstellogica. Het moet spaarzaam worden gebruikt—als frequente downcasting voorkomt, overweeg dan of een getypeerd fout-enum geschikter zou zijn.

Conversie tussen Option en Result

De standaardbibliotheek biedt methoden om te converteren tussen Option en Result, wat vloeiende compositie mogelijk maakt wanneer verschillende API's verschillende patronen gebruiken.

conversions.rsrust
// Option and Result interoperability

fn get_env_port() -> Option<u16> {
    std::env::var("PORT")
        .ok()  // Result -> Option (discards error)
        .and_then(|s| s.parse().ok())
}

fn get_env_port_with_error() -> Result<u16, String> {
    std::env::var("PORT")
        .map_err(|_| "PORT not set".to_string())?
        .parse()
        .map_err(|_| "PORT is not a valid number".to_string())
}

fn lookup_and_parse(map: &std::collections::HashMap<String, String>, key: &str) -> Result<u16, String> {
    map.get(key)
        .ok_or_else(|| format!("key '{}' not found", key))?  // Option -> Result
        .parse()
        .map_err(|e| format!("parse error for '{}': {}", key, e))
}

De ok()-methode gooit foutdetails weg wanneer alleen aanwezigheid van belang is. De methoden ok_or() en ok_or_else() converteren None naar een aangepaste fout, waardoor ?-propagatie van Option-waarden mogelijk wordt. Deze patronen komen vaak voor in pattern matching interviewvragen.

Prestatie-Overwegingen

Foutafhandeling in Rust brengt geen runtimekosten met zich mee op het succespad. Result en Option zijn stack-gealloceerde enums met deterministische grootte. De compiler optimaliseert controles weg wanneer hij kan bewijzen dat een tak onbereikbaar is.

| Aanpak | Kosten Succespad | Kosten Foutpad | |--------|-----------------|----------------| | Result/Option | Nul | Stack unwinding (goedkoop) | | panic! | Nul | Volledige stack unwinding + cleanup | | C++ Exceptions | Nul (meestal) | Dure heap-allocatie + RTTI |

unwrap() en expect() moeten worden vermeden in bibliotheekcode—ze zijn gereserveerd voor gevallen waar een falen echt een bug aanduidt. Voor prestatiekritieke paden waar fouten vaak voorkomen, overweeg het gebruik van enums met inline data in plaats van heap-gealloceerde fouttypes.

Conclusie

  • Result<T, E> behandelt herstelbare fouten; Option<T> behandelt afwezigheid—beide dwingen afhandeling af tijdens compilatie
  • De ?-operator propageert fouten beknopt en vereist From-trait implementaties voor typeconversie
  • thiserror genereert gestructureerde fouttypes voor bibliotheken zonder boilerplate
  • anyhow biedt contextuele foutketens voor applicaties, met ondersteuning voor context(), bail! en ensure!
  • Combineer beide: bibliotheken exposeren getypeerde fouten via thiserror, applicaties omhullen ze met anyhow
  • Asynchrone code gebruikt identieke patronen—? werkt in async-blokken zonder aanpassingen
  • ok_or() converteert Option naar Result; ok() voor het omgekeerde
  • Downcast omhulde fouten alleen wanneer specifieke herstellogica het originele type vereist

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Delen

Gerelateerde artikelen