Rust Error Reference

Search Rust standard-library error types, traits and common crate error variants.

A searchable reference for Rust's std::error::Error trait, io::ErrorKind variants, parse error types, and the Result/Option helpers plus anyhow and thiserror conventions. Runs in your browser. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What does the ? operator do in Rust?

The ? operator unwraps an Ok value or returns early on Err, automatically applying the From trait to convert the error into the function's declared error type. It is the idiomatic way to propagate failures without manual match statements.

This is a searchable reference for Rust error handling — the std::error::Error trait, the Result/Option enums, the io::ErrorKind variants, the standard parse error types (ParseIntError, Utf8Error), and the conventions of the widely used anyhow and thiserror crates.

How it works

Rust has no exceptions. Fallible operations return Result<T, E> (either Ok(T) or Err(E)), and absence is modelled with Option<T>. The std::error::Error trait unifies error values and exposes source() to walk a cause chain. Concrete errors like io::Error carry an ErrorKind enum — NotFound, PermissionDenied, TimedOut — so you can match on the category portably with err.kind() instead of inspecting raw OS numbers.

You propagate failures with the ? operator. Applied to a Result, it returns early on Err, automatically calling From to convert that error into the function’s declared error type. Libraries typically define their own error enum (often derived with thiserror and #[from] for those conversions), while applications box everything behind anyhow::Error and attach human-readable context().

Example

Parse a number, propagate the error, then inspect an I/O kind:

use std::io::{self, ErrorKind};

fn read_port(s: &str) -> Result<u16, Box<dyn std::error::Error>> {
    let n: u16 = s.trim().parse()?;   // ? converts ParseIntError via From
    Ok(n)
}

match std::fs::read("config.toml") {
    Ok(bytes) => { /* ... */ }
    Err(e) if e.kind() == ErrorKind::NotFound => { /* use defaults */ }
    Err(e) => return Err(e.into()),
}

Reach for unwrap() only when failure is truly impossible — otherwise prefer ?, which keeps the program recoverable instead of panicking. Everything runs in your browser; nothing is uploaded.

Choosing the right error strategy

Rust’s ecosystem has converged on clear conventions, and knowing which approach belongs where prevents the common mistake of re-designing an error system mid-project.

In a library

Define your own error enum. Use thiserror to avoid the boilerplate:

use thiserror::Error;

#[derive(Debug, Error)]
pub enum MyError {
    #[error("IO failure: {0}")]
    Io(#[from] std::io::Error),
    #[error("parse error: {0}")]
    Parse(#[from] std::num::ParseIntError),
    #[error("invalid input: {0}")]
    Invalid(String),
}

The #[from] attribute auto-generates From implementations so ? converts each sub-error into MyError at the call site. Callers can match on specific variants.

In a binary or application

Use anyhow. Return anyhow::Result<T> and add context with .context() or .with_context():

use anyhow::{Context, Result};

fn read_config(path: &str) -> Result<Config> {
    let raw = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read config from {path}"))?;
    serde_json::from_str(&raw).context("config file is invalid JSON")
}

The resulting error messages chain cleanly: “config file is invalid JSON: expected : at line 3 column 5”.

The io::ErrorKind matrix

Matching on err.kind() is the portable, OS-independent way to handle I/O errors:

ErrorKindCommon causeTypical action
NotFoundMissing file or directoryCreate default, or prompt user
PermissionDeniedOS access restrictionLog, escalate, or fail gracefully
AlreadyExistsCreating a file that existsUse create_new flag or skip
TimedOutNetwork or I/O timeoutRetry with backoff
ConnectionRefusedRemote not listeningCheck service availability
InvalidDataMalformed bytesReturn parse error to caller
UnexpectedEofStream cut shortTreat as partial data error

Common pitfalls

  • .unwrap() in production code is a panic waiting to happen. Reserve it for prototypes or genuinely impossible cases; use .expect("reason") at minimum so the panic message explains why failure should be impossible.
  • Mixing Box<dyn Error> and concrete error types in the same function makes the From chain hard to reason about. Pick one style per function signature.
  • Ignoring the cause chain: anyhow::Error stores source errors; always use {:#} (the alternate debug format) when printing, as it formats the full chain rather than just the outermost message.