Dmitrii Aleksandrov

Why Use Structured Errors in Rust Applications?

Posted on Last edited 7 mins

Tech

TL;DR I prefer thiserror enums over anyhow, even for application code that simply propagates errors. Custom error types require additional effort, but make the code easier to reason about and maintain down the road.

“Using thiserror for libraries and anyhow for applications”

In 2025, this is the conventional error handling practice in the Rust community. Let’s quickly recap the main ideas:

If you google “error handling in Rust”, the typical introductory posts are going to start with the basics of panic, Option and Result, then explain these two libraries, give this rule of thumb to choose bethween them, show some code, and stop right here.

Pattern matching isn’t the only reason to use structured errors

At work, my team has written a ~50k LoC web server in Rust. We’ve been maintaining it full-time for over a year. It mostly follows the same pattern of “just propagate the error” and almost never pattern-matches specific error conditions. The main benefit of structured errors is not applicable. It sounds like we should be using something like anyhow instead of spending time on maintaining our own error types. But I found many other benefits of doing so:

The tradeoffs

Custom errors have their drawbacks:

If your application is performance-sensitive, there are also performance considerations that don’t present a clear winner:

Assessing the tradeoff is up to you. Structured errors are worth it in my application.

To be continued

To present the full picture, I also wanted to cover:

But this saga takes too long, so these topics go to their own separate posts and this post ends here. 🚧

I have to be ✨ agile ✨ in order to actually publish anything.


Check out my first post about error handling:

I’ve also found these good posts about structured errors in Go and Haskell, but they’re quite detailed and the actual approach in the code is quite different from mine:

I’ll discuss mine in the next post in the series.

Discuss


  1. “Structured errors” = non-opaque, non-string error types. Types that have a “shape”. ↩︎

  2. This is caused by having a nominal type system in general and lacking anonymous unions in particular. ↩︎

  3. Returning an error string is a common pattern among beginners, because it’s very easy to understand and use. anyhow retains the same advantages while also supporting richer features like backtraces, error chaining and automatic conversions from library error types. ↩︎

  4. Actually, error messages are specified in the Display impl, rather than the type itself. But when using thiserror, the error messages appear as annotations on the type. ↩︎

  5. While most of this post comes from my real experience, the examples in this list are theoretical. I’ve never actually implemented these features. Please call me out if something’s wrong with this section. Also, send concrete examples, if you know any! ↩︎

  6. IMO, it’s totally worth it, because these types replace hand-written docs that list the possible errors. Type-checked docs are the best! ↩︎

  7. For dynamic errors, you usually introduce an anyhow-style dependency too. But if you want to avoid dependencies, then living with raw Box<dyn Error> is probably easier than hand-writing impls for all your custom types. ↩︎

  8. Just like with the point about “jumping to the error message”, one can argue that this is just a limitation of our current tooling, rather than a fundamental flaw of custom enums. ↩︎

  9. Currently, capturing backtraces is expensive. See backtrace-related issues in the anyhow repo. ↩︎

Comments