Why look beyond anyhow
anyhow is a Rust crate designed for application-level error handling, offering a simple and ergonomic way to propagate errors without requiring extensive custom error type definitions. It prioritizes developer convenience and quick prototyping by providing a trait object Error that can represent any error type, making it suitable for scenarios where precise error types are less critical than consistent error flow. However, this flexibility can be a drawback in libraries or components where consumers need specific, distinct error types to handle different failure modes programmatically. For example, a library might need to expose a specific FileNotFound error that can be caught and handled, rather than a generic anyhow::Error that requires runtime inspection. Additionally, some developers may prefer a more verbose, explicit error handling strategy for better compile-time guarantees, machine-readable error reporting, or integration with custom error reporting tools. In such cases, alternatives that focus on defining structured, composable error types become more appealing.
Top alternatives ranked
-
1. thiserror — Derive-based error generation
thiserrorsimplifies the creation of custom error types in Rust by providing aderivemacro. Instead of manually implementing thestd::error::Errorandstd::fmt::Displaytraits, developers can annotate an enum or struct, andthiserrorgenerates the necessary boilerplate. This approach maintains the benefits of distinct error types—allowing for programmatic error differentiation and handling—while significantly reducing the manual effort involved. It's particularly well-suited for library development, where exposing specific, well-defined error types is crucial for consumers to build robust error recovery logic. Unlikeanyhow, which often boxes errors into a generic type,thiserrorpromotes defining a precise error hierarchy, which can improve compile-time safety and provide clearer error messages.Best for: Library development, structured error types, compile-time error checking, reducing error boilerplate.
Learn more on the thiserror profile page or visit the official documentation.
-
2. snafu — Context-aware error handling
snafu(Simple Naming for Functions and Unwrapping) is a Rust crate that focuses on providing context-aware error handling. It allows developers to add contextual information to errors as they propagate up the call stack, making debugging easier by preserving the original error source and adding relevant details at each failure point.snafuachieves this through itsSnafumacro, which can be derived on error enums to define specific error variants and their associated context fields. This approach offers a middle ground between the generic flexibility ofanyhowand the strict type definitions ofthiserror. It allows for distinct error types while ensuring that each error carries meaningful diagnostic information, which is valuable for both application and library development where detailed error reporting is a priority.Best for: Contextual error reporting, detailed diagnostics, library and application use, combining distinct error types with rich information.
Learn more on the snafu profile page or visit the official documentation.
-
3. Custom Error Types — Manual implementation for full control
Implementing custom error types manually involves defining an
enumorstructfor your errors and explicitly implementing thestd::error::Errorandstd::fmt::Displaytraits. This method offers the highest level of control over error structure, reporting, and behavior. While it requires more boilerplate code compared to using crates likethiserrororanyhow, it allows for highly optimized or specialized error handling logic. For instance, developers can define custom error codes, add specific recovery methods, or integrate deeply with application-specific logging and telemetry systems. This approach is often favored in performance-critical applications, embedded systems, or projects with unique error handling requirements that are not fully met by existing libraries. It ensures that every aspect of the error is precisely tailored to the project's needs.Best for: Maximum control over error behavior, performance-critical applications, unique error handling requirements, deep integration with custom systems.
Learn more about creating custom error types in Rust.
-
4. error-chain — Older, structured error handling
error-chainis an older, but still functional, Rust crate for structured error handling. It was a prominent solution before the introduction ofstd::error::Errorstandardization and the development of newer crates likeanyhowandthiserror.error-chainprovides macros to define error types, including their kinds, associated data, and backtraces. It aims to reduce boilerplate for common error patterns and facilitate error propagation with contextual information. While it offered significant improvements over manual error implementation at the time, its reliance on macros and different idioms compared to modern Rust error handling practices has led to a decline in its adoption. Newer alternatives often provide similar or enhanced functionality with more idiomatic Rust code and better integration with the standard library's error traits.Best for: Legacy projects using
error-chain, developers familiar with its patterns, projects where migration to newer crates is not feasible.Learn more on the error-chain profile page or visit the official documentation.
-
5. Custom Result Wrappers — Tailored Result aliases
Custom
Resultwrappers involve defining type aliases forstd::result::Resultwhere the error type is fixed to a specific custom error enum or struct. For example,type MyResult. This approach is not a standalone error handling library but rather a pattern used in conjunction with custom error types (often created with= Result ; thiserroror manually). It streamlines function signatures by reducing repetition of the error type, making code cleaner and more readable within a specific module or application context. While it doesn't solve the problem of error type definition itself, it enhances the developer experience by providing a consistent and concise way to declare functions that can return errors specific to the domain. This pattern is particularly useful in applications where a single, overarching error type is sufficient for most operations.Best for: Improving readability of function signatures, enforcing a consistent error type within a module or application, used with custom error types.
Learn more about type aliases in Rust.
Side-by-side
| Feature | anyhow | thiserror | snafu | Custom Error Types | error-chain | Custom Result Wrappers |
|---|---|---|---|---|---|---|
| Primary Use Case | Application error propagation | Library error definition | Contextual error reporting | Max control, specific needs | Structured error for older projects | Code readability, consistent error types |
| Error Type Definition | Generic anyhow::Error (trait object) |
Derive macro for custom enums/structs | Derive macro for context-aware enums | Manual enum/struct implementation |
Macros for error kinds and chains | Type alias for Result with fixed error |
| Boilerplate Reduction | High (minimal setup) | High (macro-generated traits) | Moderate (macro-generated context) | Low (manual implementation) | Moderate (macros for common patterns) | High (reduces Result verbosity) |
| Contextual Information | .context() and .with_context() |
Custom fields in error enum variants | .context(), .with_context(), specific clauses |
Manual fields in error types | .chain_err() for linking errors |
Relies on underlying error type |
| Error Downcasting | Yes (.downcast_ref(), .downcast_mut()) |
No (distinct types by design) | No (distinct types by design) | No (distinct types by design) | No (distinct types by design) | Relies on underlying error type |
| Backtrace Support | Yes (optional feature) | No (relies on underlying error source) | Yes (optional feature) | Manual implementation or external crate | Yes (built-in) | Relies on underlying error type |
| Performance Overhead | Minor (dynamic dispatch for Error trait object) |
Minimal (static dispatch) | Minimal (static dispatch) | Minimal (static dispatch) | Minor (macro expansion, some dynamic dispatch) | Minimal (type alias) |
| Ecosystem Integration | Widely used in applications | Standard for libraries | Growing adoption, especially with context needs | Depends on custom implementation | Declining, but present in older projects | Common pattern across Rust projects |
How to pick
Choosing the right error handling strategy in Rust depends on several factors, primarily the nature of your project (application vs. library), the level of detail required for error reporting, and your preference for boilerplate versus explicit control.
- For Applications (like
anyhow's strength):- If your primary goal is simple error propagation, quick prototyping, and you don't need to differentiate between specific error types programmatically,
anyhowremains an excellent choice. Its.context()methods make adding diagnostic information straightforward. - If you need more structured errors even in applications, but still want to minimize boilerplate, consider combining
thiserrorfor defining a single top-level application error enum and then usinganyhowfor intermediate, less critical errors that can be converted into your main error type.
- If your primary goal is simple error propagation, quick prototyping, and you don't need to differentiate between specific error types programmatically,
- For Libraries (where
anyhowis less ideal):thiserroris generally the recommended choice for libraries. It allows you to define distinct error types that consumers of your library can match against and handle specifically. This leads to more robust and predictable APIs. Use it when you need to expose a clear error contract.snafuis a strong contender for libraries (and complex applications) when you need to provide rich, contextual information with your errors. If debugging and detailed error reporting are critical, and you want to ensure errors carry relevant data from their point of origin,snafuoffers a powerful solution.
- When Maximum Control is Needed:
- Custom Error Types (manual implementation) should be considered if you have very specific requirements that no existing crate fully meets. This might include highly optimized error paths, unique serialization formats, or deep integration with a custom error-reporting infrastructure. Be prepared for more manual coding.
- For Reducing Verbosity:
- Custom Result Wrappers are a good pattern to adopt alongside
thiserroror manual custom errors. They simplify function signatures by aliasingResultto something likeMyResult, making your code cleaner once your error types are defined.
- Custom Result Wrappers are a good pattern to adopt alongside
- For Legacy Projects:
error-chainis primarily relevant for older projects that already use it. For new projects, newer crates likethiserrorandsnafuoffer more idiomatic and often more flexible solutions.
Ultimately, the decision often comes down to a trade-off between the flexibility and low boilerplate of anyhow versus the type safety and structured reporting offered by thiserror and snafu. Many projects find a hybrid approach effective, using anyhow for internal application errors and a more structured approach like thiserror for errors exposed through public APIs.