Error handling in libraries

I have a few questions about error handling in 2023.

  1. What is the right way to attach a path to an io::Error? A NamedIoError variant with a PathBuf and the error? This seems like a really common problem; is there a wheel I can avoid reinventing?

  2. When is it appropriate to use source vs from in thiserror? If an error variant contains a T: Error, when should that T instance be returned by the source method? Relatedly, how should I think of anyhow contexts?

  3. What errors should have backtraces attached? What about async code?

  4. Are there antipatterns to avoid? Are there libraries with particularly good practices?

  5. Is it better to have more error types with fewer variants or the opposite? Should a library have one big error type or is it better to restrict each fallible function to return a type that expresses exactly what errors can occur?


Update: I found your question is close to what error-handling project group is doing. And you can leave your question there.

I don't think there's a single particular "right" way.

#[from] implies #[source]. I personally never found a compelling argument for using #[source] over #[from], because the From impl for an underlying error is good to have for easier ?-bubbling. Maybe if there's already a manual impl and it would cause a conflict, then you will find #[source] useful.

Always? I mean, knowing the underlying cause is always better than not knowing it.

In what sense?

Probably as many of them as you can achieve. Again, knowing where an error comes from is better than not knowing it.

Well, what about it?

In principle, restricting errors to only the variants that occur would be ideal. In practice, this doesn't really gain you much usually, and it's pretty annoying to implement, so most code bases end up with a single error type. Unless there is an obvious separation into a very small number of cases, e.g. serialization formats might come with separate ser::Error and de::Error types.

2 Likes

Thank you for the thorough response.

Regarding contexts, is adding a context to an error creating a new error or augmenting an existing error with some optional data? There are cases where both of these options make sense. For a library should I just stuff some context information into an extra Option<String> carried around by an error?

Regarding async, is there something I can or should add beyond a backtrace that has additional useful information? An actual backtrace looks very different than the code because of how the code is transformed by the compiler.

In a library, you should probably not be using anyhow in the first place – it's designed to be used in application code, not in libraries.

You certainly can in some cases, but it's hard to tell in general.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.