5 ms·
These discussions are always frustrating, because it feels like the people advocating Go's approach are comparing them to Java style Checked Exceptions, while t
by akavi 6y ago
These discussions are always frustrating, because it feels like the people advocating Go's approach are comparing them to Java style Checked Exceptions, while the people most annoyed with Go's approach are usually comparing them to Sum Types (like Rust's Result or Haskell's Maybe/Either).
It's made even more frustrating in that it's very difficult to convey the pragmatic differences between those two approaches to someone who hasn't had the chance to use both. Both seem to be a way of capturing errors in the type system, but the latter really is just dramatically more ergonomic.
So people talk past each other and around and around we go.
- dgb23 6y agoI agree but with a caveat. The Rust and Go error handling approach is only superficially different. Both: - are statically checked - stick out to the caller - are just values (instead of idiosyncratic control mechanisms) - allow the caller to handle them at their discretion The big difference on how you interact with errors are orthogonal to that. In Rust you get to use pattern matching and a whole bunch of useful traits and sugar, both for propagating them and for panicking. In Go you write these if-blocks. So in Rust you end up with something more involved/complex that conveys its semantics clearer. Go is much more simple and accessible but also less expressive. (Another good example of this is the whole story about iterators in Rust vs. just for-loops in Go.) From my perspective the difference in error handling is more about the languages in general, because both just give you error-values. From a general perspective. There certainly are subtleties, which I'm ignoring here.
- twic 6y ago> The Rust and Go error handling approach is only superficially different. Hold on, in the Rust world it's understood that the Rust and checked exception error handling approaches are only superficially different (hence recurring proposals to add exception-like syntactic sugar [0]). Does that mean Go's error handling approach is only superficially different to checked exceptions? [0] https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md https://github.com/rust-lang/rfcs/blob/master/text/0243-trai...
- dgb23 6y ago> in the Rust world it's understood that the Rust and checked exception error handling approaches are only superficially different Is that really so? I read this differently. The RFC you linked goes in-depth about how they are different from both normal Rust error handling and typical exception handling.
- steveklabnik 6y agoIMHO your overall thesis of "Go and Rust are very similar here" is correct, but you're missing the largest difference, IMHO. In my mind, Go and Rust's mechanisms are very close, except that: * Go returns a product type, that is, you get a value AND an error * Rust returns a sum type, that is, you get a value OR an error This doesn't mean one or the other is inherently better, though I do admit a strong personal bias for the Rust way. The tradeoff is basically that sum types really need generics to work well, IMHO. This means that Go can drop a lot of the other machinery Rust has that makes its system work. That's the core tradeoff. This matters because of, for example, your first bullet point. Yes, both languages statically check their errors, but the ways in which they do so and the properties you get from said static checking are significantly (in my mind) different.
- dgb23 6y agoThank you, this is an important distinction and informs the different structural idioms of handling these cases: In Go you get a flat recipe of instructions. You find an error then exit the function somehow, usually via returning. In the subsequent blocks the soundness is sometimes only implied AKA you 'know' you handled that error before and now you can assume that the other part of your 'product type' has a certain shape but they are inherently separated types. In Rust you get matching and function expressions (often from traits) flowing through a tree. There is a type soundness to this: Each branch in isolation is a statically checked part of the whole tree. (I have personally never used an advanced IDE for Go, but I can at least imagine that a sufficiently powerful one can catch quite a few of these cases as well.)