---
title: "Rust Error Handling: From Beginner to Production"
url: https://mdfy.app/orlirmor
updated: 2026-04-25T17:54:36.005Z
source: "mdfy.app"
---
 여기 뭐 될거 아니잖아 이제# 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)*