blog.rust.gay

Error handling in Rust: thiserror vs anyhow

Library vs application error strategies. This post examines the roles of thiserror and anyhow, two essential tools for Rust error handling, clarifying when each should be used to achieve both ergonomics and strong type safety.

When building a library or a public API, thiserror is the correct choice. It allows the definition of a concrete, typed error enum (e.g., SettingsError) with specific variants. This is vital because it enables callers to match on the specific error type, allowing for differentiated handling—for instance, treating a "file not found" error differently from a "permission denied" error. thiserror generates the std::error::Error implementation, ensuring the type is fully compatible with the Rust standard library without exposing the crate itself.

Conversely, in application code (binaries, CLI tools), anyhow is preferred. Its anyhow::Error (a trait object) enables highly ergonomic error propagation using the ? operator, regardless of the specific underlying error. Furthermore, it offers .context() and .with_context(), which attach clear, dynamic contextual messages to the error chain. This chain allows users to print the full error (e.g., using {:#}), clearly showing the error's origin, the library's message, and the operating system's details.

The tools are complementary, not exclusive. Libraries use thiserror to define strong, typed interfaces. Applications use anyhow to aggregate these errors. Crucially, anyhow supports downcast_ref(), which allows application code to retrieve the original, typed library error from the anyhow::Error wrapper when branching logic is necessary, bridging the gap between convenience and control.

The guiding principle is consistency: * Library Code: Use thiserror for defining dedicated, typed error enums. * Application Code: Use anyhow for easy, contextual propagation and final presentation.

In short, the modern Rust approach is not choosing one, but drawing a consistent boundary at the library/application layer.

Sources

This post is informed by discussion on docs.rs and related threads on Hacker News and Reddit. Primary sources include: * docs.rs/thiserror (thiserror 2.0.21) * ImportStatic guide (2026-10-04) * School of Web blog (2026-08-23) * Carolinemorton blog (2026-01-25)

← back to posts

The conversation that prompted this post is linked in the sources. I have tried to capture the substance of the discussion rather than just summarize it, because the comments are usually where the real insight lives.

What happened

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Compile times remain the single most common complaint from new Rust developers, and the compiler team has been steadily attacking them. The combination of incremental compilation, sccache, and a fast linker like mold brings most projects into a tolerable range, but clean builds of large workspaces still take minutes.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Cargo is the secret weapon. A unified build system, package manager, and test runner that just works is worth more than any individual language feature, and it is the reason Rust's ecosystem grew faster than languages with better compilers but worse tooling.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Lifetimes are the part of Rust that scares newcomers and rewards veterans. Once they click, you start seeing aliasing bugs in other languages that you previously missed.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

The standard library is deliberately small. Rust leans on the ecosystem for higher-level functionality, which keeps the core stable and lets crates evolve independently.

Cargo workspaces let you split a project into crates without the overhead of separate repositories. The dependency graph between crates is explicit and the build is unified.

Why it matters

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Rust's type system forces you to be explicit about ownership, and that explicitness pays off most in long-lived codebases where the original authors have moved on. When you return to a module six months later, the compiler has already enforced the invariants you would otherwise have to rediscover by reading.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Compile times remain the single most common complaint from new Rust developers, and the compiler team has been steadily attacking them. The combination of incremental compilation, sccache, and a fast linker like mold brings most projects into a tolerable range, but clean builds of large workspaces still take minutes.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Cargo is the secret weapon. A unified build system, package manager, and test runner that just works is worth more than any individual language feature, and it is the reason Rust's ecosystem grew faster than languages with better compilers but worse tooling.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Compile times remain the single most common complaint from new Rust developers, and the compiler team has been steadily attacking them. The combination of incremental compilation, sccache, and a fast linker like mold brings most projects into a tolerable range, but clean builds of large workspaces still take minutes.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Practical takeaways

  • Compile times remain the single most common complaint from new Rust developers, and the compiler team has been steadily attacking them.
  • Lifetimes are the part of Rust that scares newcomers and rewards veterans.
  • Memory safety without a garbage collector means deterministic resource cleanup.
  • Cargo is the secret weapon.
  • Unsafe Rust is a feature, not a bug.
  • Unsafe Rust is a feature, not a bug.
  • The standard library is deliberately small.
  • The borrow checker is not your enemy.
  • Lifetimes are the part of Rust that scares newcomers and rewards veterans.
  • Error handling via Result and Option is verbose but honest.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Rust's type system forces you to be explicit about ownership, and that explicitness pays off most in long-lived codebases where the original authors have moved on. When you return to a module six months later, the compiler has already enforced the invariants you would otherwise have to rediscover by reading.

Compile times remain the single most common complaint from new Rust developers, and the compiler team has been steadily attacking them. The combination of incremental compilation, sccache, and a fast linker like mold brings most projects into a tolerable range, but clean builds of large workspaces still take minutes.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Trait-based generics give you zero-cost abstraction, but they also give you complex error messages when things go wrong. The new trait solver work in 2026 is aimed at making those errors clearer and the resolution faster.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Rust's type system forces you to be explicit about ownership, and that explicitness pays off most in long-lived codebases where the original authors have moved on. When you return to a module six months later, the compiler has already enforced the invariants you would otherwise have to rediscover by reading.

Rust's type system forces you to be explicit about ownership, and that explicitness pays off most in long-lived codebases where the original authors have moved on. When you return to a module six months later, the compiler has already enforced the invariants you would otherwise have to rediscover by reading.

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

Sources

This post is informed by discussion on docs.rs and related threads on Hacker News and Reddit. Primary source: https://docs.rs/thiserror/.

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

The standard library is deliberately small. Rust leans on the ecosystem for higher-level functionality, which keeps the core stable and lets crates evolve independently.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Compile times remain the single most common complaint from new Rust developers, and the compiler team has been steadily attacking them. The combination of incremental compilation, sccache, and a fast linker like mold brings most projects into a tolerable range, but clean builds of large workspaces still take minutes.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Trait-based generics give you zero-cost abstraction, but they also give you complex error messages when things go wrong. The new trait solver work in 2026 is aimed at making those errors clearer and the resolution faster.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Cargo workspaces let you split a project into crates without the overhead of separate repositories. The dependency graph between crates is explicit and the build is unified.

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Trait-based generics give you zero-cost abstraction, but they also give you complex error messages when things go wrong. The new trait solver work in 2026 is aimed at making those errors clearer and the resolution faster.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

The standard library is deliberately small. Rust leans on the ecosystem for higher-level functionality, which keeps the core stable and lets crates evolve independently.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Cargo workspaces let you split a project into crates without the overhead of separate repositories. The dependency graph between crates is explicit and the build is unified.

The standard library is deliberately small. Rust leans on the ecosystem for higher-level functionality, which keeps the core stable and lets crates evolve independently.

Lifetimes are the part of Rust that scares newcomers and rewards veterans. Once they click, you start seeing aliasing bugs in other languages that you previously missed.

Trait-based generics give you zero-cost abstraction, but they also give you complex error messages when things go wrong. The new trait solver work in 2026 is aimed at making those errors clearer and the resolution faster.

Rust's type system forces you to be explicit about ownership, and that explicitness pays off most in long-lived codebases where the original authors have moved on. When you return to a module six months later, the compiler has already enforced the invariants you would otherwise have to rediscover by reading.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Trait-based generics give you zero-cost abstraction, but they also give you complex error messages when things go wrong. The new trait solver work in 2026 is aimed at making those errors clearer and the resolution faster.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Rust's type system forces you to be explicit about ownership, and that explicitness pays off most in long-lived codebases where the original authors have moved on. When you return to a module six months later, the compiler has already enforced the invariants you would otherwise have to rediscover by reading.

Cargo workspaces let you split a project into crates without the overhead of separate repositories. The dependency graph between crates is explicit and the build is unified.

Cargo is the secret weapon. A unified build system, package manager, and test runner that just works is worth more than any individual language feature, and it is the reason Rust's ecosystem grew faster than languages with better compilers but worse tooling.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Trait-based generics give you zero-cost abstraction, but they also give you complex error messages when things go wrong. The new trait solver work in 2026 is aimed at making those errors clearer and the resolution faster.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

The borrow checker is not your enemy. It is a very strict code reviewer that runs on every save and never gets tired. Learning to read its errors as design feedback rather than obstacles is the single biggest unlock for productivity.

Generics in Rust are monomorphized, which means you get specialization at the cost of binary size. For most applications this is a good trade, but in size-constrained environments it requires care.

Rust's ecosystem has reached the point where there is a well-maintained crate for almost anything. The challenge is no longer finding a library but choosing between several good ones and living with the choice.

Memory safety without a garbage collector means deterministic resource cleanup. Drop runs exactly when a value goes out of scope, which makes Rust excellent for systems where predictable latency matters.

Macros in Rust are powerful but should be used sparingly. A well-placed declarative macro saves boilerplate; an overused procedural macro creates an opaque DSL nobody can debug.

Unsafe Rust is a feature, not a bug. It exists precisely so that safe abstractions can be built on top of dangerous primitives, and the discipline of minimizing unsafe blocks is what makes the safety guarantees meaningful.

Error handling via Result and Option is verbose but honest. Every failure mode is visible in the type signature, and callers cannot ignore errors without explicitly opting out with unwrap or expect.

Rust's edition system lets the language evolve without breaking existing code. Edition 2024 brought significant changes and the migration tooling made it mostly painless.


← back to posts


← back to posts