---
title: "Rust Error Handling: From Beginner to Production"
url: https://memory.wiki/orlirmor
updated: 2026-05-11T09:07:36.559Z
hub: https://memory.wiki/hub/raymindai
concept_count: 12
source: "memory.wiki"
---
# Rust Error Handling: From Beginner to Production

Rust doesn’t have exceptions. Instead, it uses `Result<T, E>` for recoverable errors and `panic!` for unrecoverable ones.

이거 에디팅은 잘되는데 헐랭이다.

```rust
use std::fs;
use std::io;

fn read_config(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn main() {
    match read_config("config.toml") {
        Ok(content) => println!("Config loaded: {} bytes", content.len()),
        Err(e) => eprintln!("Failed to load config: {e}"),
    }
}
```

## The `?` Operator

The question mark operator propagates errors up the call stack:

```rust
fn parse_port(config: &str) -> Result<u16, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(config)?;
    let port: u16 = content
        .lines()
        .find(|l| l.starts_with("port"))
        .ok_or("missing port field")?
        .split('=')
        .nth(1)
        .ok_or("invalid format")?
        .trim()
        .parse()?;
    Ok(port)
}
```

## Custom Error Types with `thiserror`

For libraries, define explicit error types:

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApiError {
    #[error("authentication failed: {0}")]
    AuthFailed(String),

    #[error("rate limited, retry after {retry_after}s")]
    RateLimited { retry_after: u64 },

    #[error("resource not found: {resource}/{id}")]
    NotFound { resource: String, id: String },

    #[error("request timeout after {0}ms")]
    Timeout(u64),

    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

impl ApiError {
    pub fn status_code(&self) -> u16 {
        match self {
            Self::AuthFailed(_) => 401,
            Self::RateLimited { .. } => 429,
            Self::NotFound { .. } => 404,
            Self::Timeout(_) => 504,
            Self::Internal(_) => 500,
        }
    }
}
```

## Application Errors with `anyhow`

For applications (not libraries), `anyhow` provides ergonomic error handling with context:

```rust
use anyhow::{Context, Result};

async fn sync_user_data(user_id: &str) -> Result<()> {
    let profile = fetch_profile(user_id)
        .await
        .context("failed to fetch user profile")?;

    let preferences = db::get_preferences(user_id)
        .await
        .with_context(|| format!("db lookup failed for user {user_id}"))?;

    let merged = merge_data(profile, preferences)
        .context("data merge conflict")?;

    db::save(merged)
        .await
        .context("failed to persist merged data")?;

    Ok(())
}
```

## Pattern: Error Context Chain

The best production error messages tell a story:

```
Error: failed to start server

Caused by:
    0: failed to bind to 0.0.0.0:8080
    1: address already in use (os error 98)
```

## Decision Matrix

| Scenario | Use |
| --- | --- |
| Library with public API | `thiserror` + custom enum |
| Application / binary | `anyhow` + `.context()` |
| Quick prototype | `Box<dyn Error>` |
| Performance-critical path | Custom enum (no allocation) |
| FFI boundary | Error codes (integer) |

## Key Takeaways

1. **Never use** `.unwrap()` **in production code** — use `.expect("reason")` at minimum
2. **Add context at every boundary** — function calls, I/O, parsing
3. **Libraries expose types, applications consume them** — `thiserror` vs `anyhow`
4. **Errors are data** — pattern match, convert, enrich, log

---

*Further reading: [Rust Error Handling in 2026](https://blog.rust-lang.org/) | [thiserror docs](https://docs.rs/thiserror) | [anyhow docs](https://docs.rs/anyhow)*

# Rust Error Handling: From Beginner to Production

Rust doesn’t have exceptions. Instead, it uses `Result<T, E>` for recoverable errors and `panic!` for unrecoverable ones.

```rust
use std::fs;
use std::io;

fn read_config(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn main() {
    match read_config("config.toml") {
        Ok(content) => println!("Config loaded: {} bytes", content.len()),
        Err(e) => eprintln!("Failed to load config: {e}"),
    }
}
```

## The `?` Operator

The question mark operator propagates errors up the call stack:

```rust
fn parse_port(config: &str) -> Result<u16, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(config)?;
    let port: u16 = content
        .lines()
        .find(|l| l.starts_with("port"))
        .ok_or("missing port field")?
        .split('=')
        .nth(1)
        .ok_or("invalid format")?
        .trim()
        .parse()?;
    Ok(port)
}
```

## Custom Error Types with `thiserror`

For libraries, define explicit error types:

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ApiError {
    #[error("authentication failed: {0}")]
    AuthFailed(String),

    #[error("rate limited, retry after {retry_after}s")]
    RateLimited { retry_after: u64 },

    #[error("resource not found: {resource}/{id}")]
    NotFound { resource: String, id: String },

    #[error("request timeout after {0}ms")]
    Timeout(u64),

    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

impl ApiError {
    pub fn status_code(&self) -> u16 {
        match self {
            Self::AuthFailed(_) => 401,
            Self::RateLimited { .. } => 429,
            Self::NotFound { .. } => 404,
            Self::Timeout(_) => 504,
            Self::Internal(_) => 500,
        }
    }
}
```

## Application Errors with `anyhow`

For applications (not libraries), `anyhow` provides ergonomic error handling with context:

```rust
use anyhow::{Context, Result};

async fn sync_user_data(user_id: &str) -> Result<()> {
    let profile = fetch_profile(user_id)
        .await
        .context("failed to fetch user profile")?;

    let preferences = db::get_preferences(user_id)
        .await
        .with_context(|| format!("db lookup failed for user {user_id}"))?;

    let merged = merge_data(profile, preferences)
        .context("data merge conflict")?;

    db::save(merged)
        .await
        .context("failed to persist merged data")?;

    Ok(())
}
```

## Pattern: Error Context Chain

The best production error messages tell a story:

```
Error: failed to start server

Caused by:
    0: failed to bind to 0.0.0.0:8080
    1: address already in use (os error 98)
```

## Decision Matrix

| Scenario | Use |
| --- | --- |
| Library with public API | `thiserror` + custom enum |
| Application / binary | `anyhow` + `.context()` |
| Quick prototype | `Box<dyn Error>` |
| Performance-critical path | Custom enum (no allocation) |
| FFI boundary | Error codes (integer) |

## Key Takeaways

1. **Never use** `.unwrap()` **in production code** — use `.expect("reason")` at minimum
2. **Add context at every boundary** — function calls, I/O, parsing
3. **Libraries expose types, applications consume them** — `thiserror` vs `anyhow`
4. **Errors are data** — pattern match, convert, enrich, log

---

*Further reading: [Rust Error Handling in 2026](https://blog.rust-lang.org/) | [thiserror docs](https://docs.rs/thiserror) | [anyhow docs](https://docs.rs/anyhow)*

---

## Summary
Rust uses Result<T, E> for recoverable errors and panic for unrecoverable ones, with libraries using thiserror for custom error types and applications using anyhow for ergonomic error handling with context chains. Production code should avoid unwrap(), use the ? operator for error propagation, and add context at every boundary.

## Themes
- Result types over exceptions
- Error propagation patterns
- Library vs application design

## Key takeaways
- Rust uses Result<T, E> for recoverable errors and panic! for unrecoverable ones, not exceptions.
- The question mark operator (?) propagates errors up the call stack as a shorthand for match-based error handling.
- Libraries should use thiserror with custom enum types to expose explicit error variants to consumers.
- Applications should use anyhow with .context() methods to add human-readable context at operation boundaries.
- Production code must avoid .unwrap() and use .expect() with a reason at minimum.
- Error context chains create multi-line messages that tell a story from the root cause upward.

## Insights
- The document presents error handling as a choice between explicit type-based approaches (thiserror for libraries, anyhow for apps) rather than a single canonical solution, suggesting Rust's philosophy is context-dependent rather than dogmat
- Error context chains are presented as narratives that accumulate information at boundaries, transforming generic errors into actionable debugging information through layered string annotations.
- The decision matrix reveals a performance-first principle: custom enums are recommended for hot paths to avoid allocation, while higher-level abstractions prioritize ergonomics.

## Open questions / gaps
- How should errors be logged or reported to external systems once context is accumulated?

## Concepts in this document
- **Result<T, E>** _(concept)_
  The primary Rust type used for handling recoverable errors.
- **Question mark operator** _(concept)_
  Syntactic sugar for propagating errors up the call stack, enabling clean error handling chains.
- **anyhow** _(entity)_
  A crate recommended for ergonomic error handling in applications.
- **thiserror** _(entity)_
  A crate recommended for defining custom error types in libraries.
- **Error context chain** _(concept)_
  Practice of adding contextual information at each error boundary to create human-readable error stories.
- **panic!** _(concept)_
  The mechanism used for unrecoverable errors in Rust.
- **Custom error types** _(concept)_
  Enum-based error definitions that provide semantic clarity and allow pattern matching; essential for library public APIs.
- **Pattern matching** _(concept)_
  Fundamental Rust technique used to destructure and handle Result variants and custom error types.
- **Production error handling** _(tag)_
  Domain encompassing best practices for reliability, debuggability, and maintainability in shipped error-handling code.
- **Error context chaining** _(concept)_
  Pattern of adding contextual information at each boundary to create diagnostic-friendly error messages with root cause visibility.
- **Box<dyn Error>** _(concept)_
  A trait object used for simple or quick prototype error handling.
- **Result type** _(concept)_
  Rust's core mechanism for recoverable error handling that replaces exceptions with explicit Ok/Err variants.

## Concept relations (within this doc's concepts)
- **Question mark operator** enables **Error context chaining**
- **anyhow** provides via **Error context chaining**
- **Production error handling** requires **Error context chaining**
- **Custom error types** enable safe dispatch via **Pattern matching**
- **Result<T, E>** enables concise handling **Question mark operator**
- **Result<T, E>** propagates via **Question mark operator**
- **anyhow** provides ergonomic syntax **Error context chaining**
- **Question mark operator** propagates with context **Error context chain**
- **Error context chain** chains with help of **Question mark operator**
- **thiserror** provides derive macros for **Custom error types**
- **Result type** enables concise handling **Question mark operator**
- **anyhow** implements pattern via context **Error context chain**
- **Question mark operator** pairs with best practice **Error context chaining**
- **Result<T, E>** enables propagation **Question mark operator**
- **thiserror** implements pattern for **Custom error types**
- **Result type** enables propagation of **Question mark operator**
- **thiserror** implements **Custom error types**
- **anyhow** provides tools for **Error context chain**
- **Result type** contrasts with **panic!**
- **Question mark operator** pairs with for **Error context chain**

_Hub canonical:_ https://memory.wiki/hub/raymindai
_Concept digest:_ https://memory.wiki/raw/hub/raymindai?digest=1&compact=1
