In this blog post, we’ll explore strategies for streamlining error handling in Rust using two popular libraries: thiserror and anyhow. We’ll discuss their features, use cases, and provide insights on when to choose each library.
TL;DR
thiserrorsimplifies the implementation of custom error type, removing boilerplatesanyhowconsolidates errors that implementstd::error::Error- While
thiserrorprovides detailed error information for specific reactions,anyhowhides internal details
Return Different Error Types from Function
Let’s start by creating a function decode() for illustration. The function has 3 steps:
- Read contents from a file named input
- Decode each line as a base64 string
- Print each decoded string
The challenge is determining the return type for decode since std::fs::read_to_string(), base64 decode(), and String::from_utf8() each return different error types.
| |
One approach is to use trait object: Box<dyn std::error::Error>. This works because all those types implement std::error::Error.
| |
While this is suitable in some cases, it limits the caller’s ability to discern the actual error that occurred in decode(). Then, using enum is a good approach if it is desired to handle each error in different ways.
| |
By implementing std::error::Error trait, we can semantically mark AppError as an error type.
| |
However, this code doesn’t compile because AppError doesn’t satisfy the constraints required by std::error::Error, implementation of Display and Debug:
| |
The definition of std::error::Error represents the consensus of minimum requirements of an error type in Rust. An error should have two forms of description for users (Display) and programmers (Debug), and should provide its root cause.
| |
The code will be like this after implementing the required traits:
| |
Finally, we can use AppError in decode():
| |
map_err() is used to convert std::io::Error to AppError::ReadError. To use ? operator for better flow, we can implement From trait for AppError:
| |
We did several things to use our custom error type fluently:
- implement
std::error::Error - implement
DebugandDisplay - implement
From
These can be verbose and tedious, but fortunately, thiserror automatically generates most of them.
Remove Boilerplates with thiserror
The code above is simplified using thiserror:
| |
#[error] macro generates Display, #[from] macro handles From implementations and source() for std::error::Error. The implementation of Debug remains to provide detailed error messages, but #derive[Debug] can also be used if it’s enough:
| |
Deal with Any Error with anyhow
anyhow offers an alternative method for simplifying error handling, which is similar to Box<dyn std::error::Error>> approach:
| |
It compiles since types implementing std::error::Error can be converted to anyhow::Error. The error message will be like:
| |
For enhanced error messages, context() can be used:
| |
Then, the error message will be:
| |
Now our error handling is streamlined thanks to the anyhow’s type conversion and context().
Comparison between thiserror and anyhow
While thiserror and anyhow might seem similar, they serve different purposes. thiserror is suitable when users need to react differently based on the actual error type. On the other hand, anyhow is effective when internal details can be hidden from the user.
In this sense, it’s often said that thiserror is for a library, and anyhow is for an application. This saying is true to some extent, considering that library developers tend to want to give precise information to users (programmers), and applications don’t have to show detailed error information to their users.
Conclusion
In conclusion, we’ve explored the distinctive features of thiserror and anyhow and discussed scenarios where each library shines. By choosing the right tool for the job, Rust developers can significantly simplify error handling and enhance code maintainability.
thiserrorsimplifies the implementation of custom error typesanyhowintegrates anystd::error::Errorthiserroris ideal for library development where detailed information is beneficial for users (programmers).anyhowis Suited for applications where internal details are not crucial, providing simplified information to users.