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.

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.
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.
// 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.
// 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.
// 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.
// 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.
// 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;// 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.
// 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.
// 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.
// 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 vereistFrom-trait implementaties voor typeconversie thiserrorgenereert gestructureerde fouttypes voor bibliotheken zonder boilerplateanyhowbiedt contextuele foutketens voor applicaties, met ondersteuning voorcontext(),bail!enensure!- Combineer beide: bibliotheken exposeren getypeerde fouten via
thiserror, applicaties omhullen ze metanyhow - Asynchrone code gebruikt identieke patronen—
?werkt in async-blokken zonder aanpassingen ok_or()converteertOptionnaarResult;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

Rust smart pointers uitgelegd: Box, Rc, Arc en RefCell in 2026
Rust smart pointers Box, Rc, Arc en RefCell uitgelegd met compileerbare 2026-voorbeelden, een beslistabel en veelvoorkomende sollicitatievragen.

Rust 2026 Editie: Traits, Generics en Geavanceerde Sollicitatievragen
Rust traits en generics met de nieuwste 2024 Edition-features: trait upcasting, AsyncFn-closures, RPITIT en geavanceerde patronen. Inclusief sollicitatievragen.

Rust voor het Web: Actix Web vs Axum – Vergelijking en sollicitatievragen 2026
Een praktische vergelijking van Actix Web 4.13 en Axum 0.8 voor Rust-webontwikkeling in 2026. Architectuur, prestaties, ontwikkelaarservaring en sollicitatievragen voor backend Rust-posities.