3 ms·
My controversial take on Rust errors is to use anyhow everywhere until you have an immediate demand for explicit Enums. YANGNI The pros for using anyhow are bi
by athrowaway3z 10mo ago
My controversial take on Rust errors is to use anyhow everywhere until you have an immediate demand for explicit Enums. YANGNI
The pros for using anyhow are big: Easily stack errors together - eg file.open().context(path) -, errors are easily kept up to date, and easy to find where they occur.
An enum error is pleasing when you're finished, but cumbersome in practice.
It's niche situation that you need to write a function that exposes a meaningful Error state the caller can branch on. If the return state is meaningful, you usually don't put it in the Err part. eg parsers using { Ok,Incomplete,Error }. IMO Error enums are best for encoding known external behavior like IO errors.
For example: The Sender.send_deadline returning { Timeout, Closed } is the exception in being useful. Most errors are like a Base64 error enums. Branching on the detail is useless for 99.99% of callers.
i.e. If your crate is meant for >1000 users, build the full enum.
For any other stuff, use anyhow.
- zaphar 10mo agoIf your error domain has only one useful category why not just create an error type with a useful message and be done with it. Why use anyhow at all? You are essentially saying the error domain is small so the work is tiny anyway. anyhow seems useful in the very top layers where you basically just want bubble the useful errors modeled elsewhere to a top layer that can appropriately handle them. I don't think a crate should abdicate from modeling the error domain any more than they should abdicate from modeling the other types.
- athrowaway3z 10mo agoI'm trying to describe a rather large spectrum of situations, and how most of them are favorable (or not unfavorable) to anyhow. I'm not saying the error domain is small per se. Instead, one argument I'm making: what you're describing about errors bubbling up to the top layer, is what happens with the overwhelming majority of errors in my experience. Whether the error space is large or small, just wait until you have an immediate need to treat one error different from the rest. It happens, it's just not common. I didn't steelman the case for when to use enums, but in short: In a tiny error space or as valuable documentation. (The doc value is undermined somewhat when projects use 1-big-error and functions that eg only returns 3 of the 6 possible errors) I'm not advocating removing an Error enum. Just that writing one can and should be postponed, saving a lot of maintenance edits.
- zaphar 10mo agowhat you're describing about errors bubbling up to the top layer, is what happens with the overwhelming majority of errors in my experience. I agree that this is what happens in practice for most code that I read and have to interact with. I think where I differ from you is that I don't think this is good and do not advise people to do this for their own code. I think it's a pervasive but bad practice in our line of work.
- fauigerzigerk 10mo ago>I don't think a crate should abdicate from modeling the error domain any more than they should abdicate from modeling the other types. Yes, it's just harder. We usually have a pretty good idea what callers want from the happy path, but the range of things that callers may or may not want to do in case of an error is very broad.
- zaphar 10mo agoThis is an interesting perspective. I usually don't try to imagine how someone should handle the error. Instead I try to indicate the different types of errors that could occur and what type of information needs to be included to be useful to the caller. I can leave the question of what to do with that information to the caller since it's highly situational and the context necessary to make that decision lives outside of the code where I am modeling the error.
- burntsushi 10mo agoThat can be tricky because there may be a trade-off between error message quality and something else. Like, perhaps, the size of an error, code size or even runtime performance. Another trade-off with too-detailed errors---especially when those details are part of the library API---is that they become an extensibility hazard. Unless you're extremely certain about the line that divides a specific implementation from the logical domain of errors for a particular operation, you might get that wrong. And changing the implementation in the future might want a change in the error details. This is very hand-wavy, but I think we're talking at a very high level of abstraction here. My main point is to suggest that there is more of a balancing act here than perhaps your words suggest.
- zaphar 10mo agoI agree that it's a balancing act. I just don't think you get to abdicate from doing that balancing act and getting the balance wrong has consequences just like getting the balancing act wrong in your non error data model.
- 10mo ago
- sunshowers 10mo agoLLMs are pretty good at generating and maintaining error enums for you these days.
- phire 10mo agoYeah, I’m the same. I default to anyhow unless I need a strong API boundary (like if I’m publishing a library crate) Sure, it’s slightly more error prone than proper enum errors, but it’s so much less friction, and much better than just doing panic (or unwrap) everywhere.
- amluto 10mo agoEEXIST. Lots of IO operations can fail because the destination already exists, and in quite a few cases, this isn’t really a failure so much as just a different outcome. In general, there is quite a bit of code that wants to branch on the specific error reported for an I/O operation. Also, database errors. While the specific error may not be important, knowing whether an error means that a transaction definitely did not commit is valid, as is knowing whether retrying the transaction is likely to be useful. (The common case is retrying transactions that failed due to deadlock.)
- phiresky 10mo agoEven then you can just err.downcast_ref::<std::Io::Error>() though to get the underlying IOError, no?