DEV Community

Brian Maher
Brian Maher

Posted on

Rust Error Handling

When I first started writing Rust code, I went through several iterations of error handling. From that experience, I have these learnings:

  • Embrace the Result<R, E> type for general purpose error handling. Note that "stack frames" will be lost in this error handling unless you do something about it.
  • Embrace panic!() (or that family of error handling) for asserting preconditions in your code or really any error that could only happen if the program was incorrect. The beauty of a panic is the you will get a backtrace if the RUST_BACKTRACE environment variable is set.
  • When you are uncertain, use Result<R, E>, as that can easily be converted into a panic!() by the caller.

The next natural question is what type to use as the E parameter of Result<R, E>.

When designing any error type, I consider these things:

  • Make sure it is an "owned" type, there are rare exceptions to this rule, but working with non-'static errors is difficult and should be avoided.
  • When you have a function A that only triggers an error when calling function B, it is convenient to "pass the buck" and simply use ? to propagate the exact same error. However, before blindly doing this ask yourself if there is critical contextual information which may be lost. For example, if you are calling function B on every element of a collection, you may want to add contextual information about which element that error originated from. In this example, you may want to .map_err() to a new error type that attaches the relevant contextual information.
  • Consider what types of errors a caller may want to discern between. For example, if doing any sort of I/O operation, knowing that a network stream was interrupted vs invalid input being provided are typically going to be handled differently by callers since an interrupted network stream may be resolved with a retry.
  • Keep the errors constrained to only the set of errors that are actually possible. Note that this flies in the face of many popular crates that define a single error type for their entire crate (an anti-pattern in my opinion). This does two things:
    • If a new error variant is introduced or one is removed, you get compile failures. This is much better than getting unexpected behavior at runtime! If a variant is removed, the caller may also be able to reduce their error handling code.
    • Users of your code have much better signal on what errors actually need to be handled vs errors that are simply possible because of a poorly designed error.
  • Write error messages for humans and avoid collecting "stack traces". If you want an error with a stack trace, maybe you really want a panic!() instead.

In order to make it easier to create my error types thiserror is enormously valuable in reducing the boilerplate code.

Top comments (0)