Cover

Error handling is an indispensable part of Rust development. Rust’s Result provides fundamental support, but the specific implementation varies depending on the scenario. This article introduces three commonly used error handling tools—anyhow, thiserror, and snafu—analyzing their characteristics and applicable scenarios, and helps you understand through practical examples how to use them in projects. Whether you are developing applications or writing libraries, this article can serve as a valuable reference.

This article delves deeply into the three major tools for Rust error handling:

  • anyhow is suitable for rapid and unified error handling, ideal for application development;
  • thiserror supports customized error types, making it suitable for library development;
  • snafu provides context-driven error management, appropriate for complex systems.

Through comparing their strengths and weaknesses and demonstrating actual code examples, we will show you how to choose the right tool based on project needs. We also provide project setup steps and example code to help you handle errors more effectively in Rust.

Rust Error Handling

Error Handling: anyhow, thiserror, snafu

  • anyhow: unified and simple error handling, suitable for application-level programming
  • thiserror: customized and rich error handling, suitable for library-level programming
  • snafu: finer-grained error management

Note: When developing, pay attention to the size of Result.

anyhow Error: Application-level Error Handling

Conversion and unified error handling with anyhow::Error:

  • Provides a unified anyhow::Error type, supporting any error type that implements std::error::Error
  • Uses the ? operator for automatic error propagation, simplifying multi-layer nested error handling
  • Supports adding dynamic context (via the context() method) to enhance error readability
fn get_cluster_info() -> Result<ClusterMap, anyhow::Error> {  // Error 3: Err3
  let config = std::fs::read_to_string("cluster.json")?;    // Error 1: Err1
  // let config = std::fs::read_to_string("cluster.json").context("...")?;    // Error 1: Err1
  let map: ClusterMap = serde_json::from_str(&config)?;     // Error 2: Err2
  Ok(map)
}

struct Err1 {...}
struct Err2 {...}

match ret {
  Ok(v) => v,
  Err(e) => return Err(e.into())
}

Err1 => Err3: impl From<Err1> for Err3
Err2 => Err3: impl From<Err2> for Err3

impl From<Err1> for Err3 {
  fn from(v: Err1) -> Err3 {
    ...
  }
}

thiserror Error: Defining Library-level Errors

  • Automatically generates error types that conform to std::error::Error via macros
  • Supports nested error sources (using the #[from] attribute) and structured error information
  • Allows customization of error message templates (e.g., #[error("Invalid header: {expected}")])

Reference: Rust std::error::Error trait documentation

Since the Error trait requires implementing both Debug and Display:

pub trait Error: Debug + Display {

It can be printed like this:

Error -> println!("{}/ {:?}", err)

snafu Error: Context-driven Error Management

  • Converts underlying errors into domain-specific errors through the Snafu macro
  • Supports attaching structured context (such as file paths, input parameters) in the error chain
  • Provides the ensure! macro to simplify condition checking and error throwing

thiserror vs snafu

For more information, refer to: kube-rs/kube discussion #453

Comparison and Selection Guide

Dimension anyhow thiserror snafu
Error Type Unified dynamic type Static custom type Domain-driven type
Context Support Dynamic string Struct fields Structured fields + dynamic templates
Suitable Stage Application development (rapid iteration) Library development (stable interfaces) Complex systems (maintainability)
Learning Curve Low (no need to predefine types) Medium (requires designing error structures) High (requires understanding context models)
Typical Users Frontend developers / scripting tool developers Framework developers Infrastructure engineers

Practical Implementation

Create and Initialize Project rust-ecosystem-learning Based on a Template

cargo generate --git [email protected]:qiaopengjun5162/rust-template.git
cd rust-ecosystem-learning
code .

Project Directory Structure

rust-ecosystem-learning on  main [!] is 📦 0.1.0 via 🦀 1.85.0 via 🅒 base
➜ tree . -L 6 -I 'target|coverage|coverage_report|node_modules'

.
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── _typos.toml
├── cliff.toml
├── deny.toml
├── docs
└── src
    ├── error.rs
    ├── lib.rs
    └── main.rs

3 directories, 12 files

Add Dependencies

cargo add anyhow
cargo add thiserror
cargo add serde_json

main.rs File

use anyhow::Context;
use rust_ecosystem_learning::MyError;
use std::fs;
use std::mem::size_of;

fn main() -> Result<(), anyhow::Error> {
    println!("size of anyhow::Error: {}", size_of::<anyhow::Error>());
    println!("size of std::io::Error: {}", size_of::<std::io::Error>());
    println!(
        "size of std::num::ParseIntError: {}",
        size_of::<std::num::ParseIntError>()
    );
    println!(
        "size of serde_json::Error: {}",
        size_of::<serde_json::Error>()
    );
    println!("size of string: {}", size_of::<String>());
    println!("size of MyError: {}", size_of::<MyError>());

    let filename = "non_existent_file.txt";
    let _fd =
        fs::File::open(filename).with_context(|| format!("Cannot find file: {}", filename))?;

    fail_with_error()?;
    Ok(())
}

fn fail_with_error() -> Result<(), MyError> {
    Err(MyError::Custom("This is a custom error".to_string()))
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

lib.rs File

mod error;

pub use error::MyError;

error.rs File

use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Parse error: {0}")]
    Parse(#[from] std::num::ParseIntError),

    #[error("Serialize JSON error: {0}")]
    Serialize(#[from] serde_json::Error),

    // #[error("Error: {a}, {b:?}, {c:?}, {d:?}")]
    // BigError {
    //     a: String,
    //     b: Vec,
    //     c: [u8; 64],
    //     d: u64,
    // },

    #[error("Error: {0:?}")]
    BigError(Box<BigError>),

    #[error("An error occurred: {0}")]
    Custom(String),
}

#[derive(Debug)]
pub struct BigError {
    pub a: String,
    pub b: Vec<String>,
    pub c: [u8; 64],
    pub d: u64,
}

Cargo.toml File

[package]
name = "rust-ecosystem-learning"
version = "0.1.0"
edition = "2021"
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.97"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.140"
thiserror = "2.0.11"

Running the Project

rust-ecosystem-learning on  main [!] is 📦 0.1.0 via 🦀 1.85.0 via 🅒 base took 2.9s
➜ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/rust-ecosystem-learning`
size of anyhow::Error: 8
size of std::io::Error: 8
size of std::num::ParseIntError: 1
size of serde_json::Error: 8
size of string: 24
size of MyError: 24
Error: Cannot find file: non_existent_file.txt

Caused by:
    No such file or directory (os error 2)

Summary

Rust’s error handling tools each have their own focus:

  • anyhow is simple and efficient, suitable for application development.
  • thiserror is structured and clear, suitable for library design.
  • snafu offers rich context, making it ideal for complex scenarios.

Through the analysis and practical examples in this article, you can choose the appropriate error handling solution based on your actual needs.

Proper error handling makes your code more robust — practice it and elevate the quality of your Rust projects!


We are Leapcell, your top choice for hosting Rust projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog