← Back to context

Comment by mswphd

1 hour ago

a few things

1. thiserror just does codegen of the "standard" enum things people do. if you find debugging thiserror difficult, just write out the enums manually. sure it's uglier, but (roughly) equivalent. so its preferable as synctatic sugar for enums, but doesn't have any technical benefits (in the same way that syntatic sugar never really does).

2. for boxed errors, you only get allocations on your error path. Hopefully this is a cold path so it shouldn't matter.

There is a general theme behind rust error handling though which it can be good to internalize. In particular, the more details of your errors you encode in the type system, the more powerful things are. Any error type could just be

pub struct MyError(String)

the issue is that this gives very little information to a caller on what to do with your error. If you have no callers (e.g. are making a binary) it's fine, and (roughly) what `anyhow` does.

That all being said, when designing errors a natural question to ask is "can my caller do anything meaningful with this error"? For example, in Rust stdlib, `Vec::push` can allocate. This allocation can fail, which panics. "Proper" error handling would use the fallible allocation API, and propagating an OOM error or whatever through results. For most applications, this is not an error that is worth investing that much time into guarding against, so using the (potentially panicing) `Vector::push` makes things easier.

You can take this same perspective in other settings as well, in particular separating out errors into

1. structured data, that a caller should be able to extract/process to handle the error, and 2. unstructured data, that is more used for logging, and you expect the caller to pass up the call stack without inspecting themself.

Handling both types of these errors with `thiserror` can be tedious for little benefit. I've found it useful to instead solely use `thiserror` for category 1, and category 2 does other things. This could be using `.expect(...)`. There are some crates that make this nicer (e.g. `error_stack`). But the point is that it can significantly clean things up if you only encode in your error enums failures that you expect someone to handle, rather than just e.g. log.

This does somewhat validate your point that the "right way" I've been experimenting with (and mentioned above) is not just "use this-error".

Also: a big issue with `thiserror` is the tedium of handling the large error enums (or giving up on using it "properly" and shoving together multiple error variants in some unstructured error type). that is somewhat better in the LLM era, as you can have the LLM handle the tedium.