5 ms·
> they are much easier to reason about for me as a programmer reading and writing code, I would hate to have to reason about the 250 possible errors coming fro
by jcelerier 6y ago
> they are much easier to reason about for me as a programmer reading and writing code,
I would hate to have to reason about the 250 possible errors coming from every single library underneath my app, especially when the immense majority of those are better left to be solved by the user (e.g. file cannot be opened because the usb stick was removed, audio device unavailable because you are using a pro audio driver that locks to a single app, the GPU cannot be opened because I did a driver update and must reboot, etc etc).
Monadic error handling is fine when you have a finite, predetermined set of "things that can go wrong" - e.g. you're writing a small input parser for a text field, either it parses or it does not. But imho this is a microscopic amount of cases compared to all the "random stuff that can go wrong" in the average GUI app. And as a GUI app user, I really really hate when the app tries to do what's best for me - autoreconnect, write to another file, change the settings automatically, whatever. Just gimme an error popup and let me decide.
> produces code with easier execution flow
In C++ using RAII objects for state makes that concern pretty much disappear imho. Save your state & rollback in a tiny type's ctor/dtor and you're good to go, whatever you do, things can throw and what you were doing will be rolled back. Not as good as hardware transactional memory, sure, but apparently not a lot of CPU engineers are able to make that one work yet...
> (3) uses the type system to specifically+exhaustively communicate the possible outcomes.
back to 1, this is what I really do not want. Just write the happy / known-to-the-programmer path and let what you don't know about bubble up to main() or the top of your event loop.
- masklinn 6y agoThe application use-case is basically what `Box<dyn Error>` (and anyhow which is very much intended as a better version thereof) is for. If you're writing an application and don't really care about the error types, just use `anyhow::Result` (or typedef your own `Result<T, Box<dyn Error>>` to avoid a dependency) as your error type and bubble everything up to main. Then if there are cases where you want to handle specific errors… you can still do that, easily, with the help of the compiler.
- jcelerier 6y agothat's a lot of characters added for essentially the same result - exceptions were introduced because bubbling things up explicitely is a pain !
- masklinn 6y ago> that's a lot of characters added Not really. > for essentially the same result With the huge differences that 1. it signals that things can fail, which largely doesn't exist with unchecked exceptions unless you re-add that information which is going to lead to similar amounts of "characters added" (e.g. “throws”) 2. you're explicitly opting into the costs and information-discarding effects, which is rather important for the language's purported goals, it supports that use-case without discarding its other important use-cases > exceptions were introduced because bubbling things up explicitely is a pain ! Not really either, exceptions were introduced to structure error handling rather than use ad-hoc and generally in-band methods, or unrestricted jumps. They also provided a very useful hook point to provide contextual information (by default or otherwise) which the aforementioned ad-hoc methods didn't account for e.g. not much contextual data you can bundle in a shell or C error code. Leveraging sum types for error handling is an oddly recent development, both because most languages don't have sum types, and because that it could be used thus doesn't seem like a very old realisation at large (just look at error handling in Haskell).
- saurik 6y agoThat structuring is important though, and is what Haskell does with monads and C++ does with exceptions (which are in many ways a kind of hardcoded syntax for an error monad). Rust just has unstructured manual error checking and propagation with some conventions to make it more likely to be sane, as it rejected both exceptions and monads as approaches to real structured error management.
- masklinn 6y agoNo, Rust has structured error checking, in fact is specifically has monadic error management, just without the monad abstraction itself.
- thinkharderdev 6y agoThose seem like orthogonal concerns. With both Monadic error handling and exceptions you can defer handling any error until you hit some boundary that you choose. Likewise you can still choose to subsume specific errors into types that encompass lots of different specific error conditions. So the file cannot be opened, audio unavailable, GPU cannot be opened can still all be an IOError. > Just write the happy / known-to-the-programmer path and let what you don't know about bubble up to main() or the top of your event loop. I believe that is the point, an IO exception o some sort is a known exception case so the return type of the function should express that. This function can return either the contents of a file or an IOError if the file cannot be read for some reason. The caller can then decide whether they want to try and handle the exception case or ignore it and let it bubble up the stack, but the function should clearly express that it is a possible outcome.
- littlestymaar 6y ago> I would hate to have to reason about the 250 possible errors coming from every single library underneath my app That's is why I think exception are bad: the programmer usually ends up not thinking about the errors that can occur (and they often cannot even do it if they wanted because exceptions can come from anywhere) and just propagates to the user, who often doesn't even have access to the documentation of the underlying library, and must perform Google search on some cryptic error message, just to discover that the program is failing because some log formatting library he doesn't care about cannot have access to the configuration file it expected. This is a terrible user eperience. And yes, checking 250 different kinds of error can be tedious (but it should never be so high anyway), but at least with sum types it's doable because the type system is here to help you cover every case. And then you can manually check which error is a reasonable one to return to your user and which is a recoverable one that should need no external intervention. If the USB drive has been pulled out too quickly, just tell it to the user instead of returning a “aborted: did not have the rights to write to the file” exception.
- jcelerier 6y ago> and just propagates to the user, who often doesn't even have access to the documentation of the underlying library, and must perform Google search on some cryptic error message, just to discover that the program is failing because some log formatting library he doesn't care about cannot have access to the configuration file it expected. This is a terrible user eperience. you know what is even worse ? the programmer "handling" the error and just displaying a "we're sorry, an error has occured" message to the user who now has exactly zero way to know what is going wrong. Or as seems to be common in Rust, Go, and other exception-free systems, just calling panic! ... yay, "error handled". It is very irrealistic and presumptuous to assume that your program has enough domain knowledge about its users's systems to handle every single error.
- Ar-Curunir 6y agoYou don't have to handle those errors if you don't want to; you can just use `Box<dyn Error>` to propagate them without caring.
- reader_mode 6y ago>I would hate to have to reason about the 250 possible errors coming from every single library underneath my app, especially when the immense majority of those are better left to be solved by the user (e.g. file cannot be opened because the usb stick was removed, audio device unavailable because you are using a pro audio driver that locks to a single app, the GPU cannot be opened because I did a driver update and must reboot, etc etc). Depends on the ergonomics - if it's easy to convert errors to panics then you get that in application code while also explicitly enumerating error cases as an API author. I haven't used Rust enough seriously to comment authoritatively on this but it seems like a good trade off - I hate it when the library I'm using doesn't document error cases