6 ms·
The error handling story in Go had started to bother me more and more over the years. Not that it was that bad, it just felt cumbersome. And then I started le
by ansible 8y ago
The error handling story in Go had started to bother me more and more over the years. Not that it was that bad, it just felt cumbersome.
And then I started learning Rust for embedded development. I basically love the entire language, and there are very, very few design decisions I disagree with.
The error handling story is much better there, and if they're making breaking changes in Go 2.0, I'd suggest they look there for a better path forward.
> Panic-Driven Error Handling
I'd insert a gif of Vader at this point, from the end of Episode 3.
I strongly disagree with this, using 'panic' should be a last resort in most situations, when the program has no reasonable way to recover from an error condition. File not found, user error, etc. are all common occurrences which should be handled by the normal mechanisms. And this goes double for library code.
- weberc2 8y agoI mostly agree with this (especially that panic should not be used for error handling, but only for exceptional circumstances). I think checked enum style errors are the way to go, but I also think that Go's approach suffices. A popular criticism of Go's approach is that the compiler doesn't force you to handle your errors, which is true, but I find that Go's culture suffices here--the biggest complaint I have about Go's error handling is that you have to use a library to get stack traces (which is also true for Rust) and stack traces are simply very practical. The other popular criticism of Go's error handling is that it's verbose, which I wholly disagree with--the verbosity is such a negligible cost that I would much prefer verbose error handling to the language complexity required to elide them (especially if the proposed sugar required baking "errors" into the language as opposed to treating them like any other data).
- ansible 8y ago> A popular criticism of Go's approach is that the compiler doesn't force you to handle your errors, which is true, but I find that Go's culture suffices here... The Go culture does suffice, but the Rust culture is even stronger here. Everyone is expected to use Result for something that can fail, which I like a lot for consistency's sake. > The other popular criticism of Go's error handling is that it's verbose, which I wholly disagree with--the verbosity is such a negligible cost that I would much prefer verbose error handling to the language complexity required to elide them (especially if the proposed sugar required baking "errors" into the language as opposed to treating them like any other data). Six months ago I would have agreed with you. And then I learned about error propagation using the question mark operator in Rust: https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator https://doc.rust-lang.org/book/second-edition/ch09-02-recove... Arguments can be made about creating custom error messages at each call level, but is that really necessary most of the time?
- weberc2 8y ago> Six months ago I would have agreed with you. And then I learned about error propagation using the question mark operator in Rust: https://doc.rust-lang.org/book/second-edition/ch09-02-recove.. https://doc.rust-lang.org/book/second-edition/ch09-02-recove.... Arguments can be made about creating custom error messages at each call level, but is that really necessary most of the time? No, this is not necessary most of the time, and I don't consider the "custom error messages" argument to be a very good argument against sugar. The convincing argument in my mind is that unlike Rust, Go has no trait system to facilitate this sugar, so the alternative is to special-case the compiler to support the error type. Of course, Go could implement a trait system, but "error handling sugar" is a negligible factor in whether or not such a system should exist and how it should be designed. TL;DR--what makes sense for Rust may not make sense for Go.
- jerf 8y ago"The convincing argument in my mind is that unlike Rust, Go has no trait system to facilitate this sugar, so the alternative is to special-case the compiler to support the error type." What do you need out of "traits" that "interfaces" don't provide, for error handling?
- weberc2 8y agoThere isn't infrastructure in the Go compiler for mapping a given sugar to a particular interface. The point isn't traits vs interfaces, but rather that the infrastructure exists in Rust and it doesn't exist in Go (and adding it to Go solely to alleviate a tiny bit of boilerplate is a bad idea).
- jerf 8y ago"And then I learned about error propagation using the question mark operator in Rust:... Arguments can be made about creating custom error messages at each call level, but is that really necessary most of the time?" Actually, one of the things that has prevented Go from "simply" or "just" solving the error propagation problem is precisely the recognition that in real code, if err != nil { return err } becomes problematic at scale, because it obscures the history of the error. Stack traces are great for humans, but not terribly useful for programmatic interpretation. Personally, I still tend to use "if err != nil { return err }" as my sort of default sketch implementation, but I find it is very common for many of those to grow something else before the program ships, and I'm still not all that great or consistent about layering errors properly anyhow. I'd suggest to a lot of people that it may be worth reading or re-reading the Go 2 error proposal [1], and to try to come at it with some relatively fresh eyes, rather than reading it for "does this precisely conform to my preconceived notions about what error handling should be?" (Another interesting aspect is around providing some base API for errors, and processing composite errors, rather than expecting end-users to do all the definition [2]. Personally I consider both important.) There is some legitimately interesting discussion and work around what to do, based on waiting to gather a lot of data about error handling before just slamming a solution into place, moreso than may meet the eye. I can't speak to Rust's solution in practice, but having used Haskell for a long time with union types for errors, I can say they are hardly error-handling nirvana there. If I'm reading the Rust documentation correctly, they're going to have the same problems in Rust as they do in Haskell. [1]: https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md https://go.googlesource.com/proposal/+/master/design/go2draf... [2]: https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md https://go.googlesource.com/proposal/+/master/design/go2draf...
- masklinn 8y ago> I would much prefer verbose error handling to the language complexity required to elide them Rust's "language complexity required to elide them" is quite literally a single postfix operator, which was added as an alternative to a small macro whose behaviour had proved wildly popular.
- weberc2 8y agoMy comment wasn't meant as a criticism of Rust. Rust's trait system does the heavy lifting for enabling this operator (and all kinds of sugar, for that matter), so this makes sense for Rust. Go doesn't have a trait system (though it could well be a useful addition!) or a macro system, so the alternative is to treat errors as a special type instead of plain-old-data as they are today.
- masklinn 8y ago> the alternative is to treat errors as a special type instead of plain-old-data as they are today. They're already a special type to an extent, since Go's MRV are not first-class it could add builtin facilities applicable to any 1..n-ary return value whose last value is either an Error or an Error-implementing struct or somesuch.
- weberc2 8y ago> They're already a special type to an extent This is news to me. How so? > since Go's MRV are not first-class it could add builtin facilities applicable to any 1..n-ary return value whose last value is either an Error or an Error-implementing struct or somesuch. MRV is "first-class" for the purposes of this discussion. Your proposal still depends on treating `error` as a special type, so my criticisms still apply--the juice just isn't worth the squeeze. That said, if another mechanism is introduced (for example, a trait-like system for hanging sugar off of a la Rust's), then the calculus changes; however, as previously mentioned, error handling sugar is not a good reason to add such a system.
- masklinn 8y ago