5 ms·
Errors are values. https://go.dev/blog/errors-are-values https://go.dev/blog/errors-are-values "Whatever you do, always check your errors!"
by cratermoon 3y ago
Errors are values. https://go.dev/blog/errors-are-values https://go.dev/blog/errors-are-values
"Whatever you do, always check your errors!"
- uticus 3y agoAs an outsider (C#/Java) this has always intrigued me. I can only imagine what my opinion would be after using it for a few months with a team.
- all2 3y agoI've been pondering doing a short experiment where I abandon exceptions in C# and instead always return 1 or 2 values from my methods: maybe some output and an error object. I'm not sure how well this will work in practice. One issue that occurs to me immediately is that I'd be wrapping a lot of library functions in try/catch blocks just to push my error objects back up the call stack. I suppose the lazy option is to just ignore the libraries and only worry about my own code.
- bananapub 3y agoI predict you'll give up because: 1. it's very very tedious to have two unrelated error systems in one language (yours and the one everyone else in C# uses) 1. as Go demonstrates, this type of error handling is extremely tedious even by itself
- all2 3y ago> this type of error handling is extremely tedious even by itself But is the tedium worth it?
- jakewins 3y agoI’ve done this at times in Python and C#, I really really miss Go or - better - Rust style errors there. But, exactly like you say, you end up fighting libraries - particularly the standard libraries, and it ain’t a fight you will win. In typescript it actually kind of works - better at least. JS APIs often are errors-as-values already because of the old continuation async APIs, and typescript of course lends itself incredibly well to rich return types.
- metaltyphoon 3y agoC# doesn’t really offer language syntax to deal with self many DUs. So you will probably suffer. Perhaps it can change if the language supports DU in the future.
- uticus 3y agoOne of the things that intrigues me about Go is that it is not meant to be used in isolation. So, the experiment you list here is actually a whole different experiment if done in isolation (on like say a hobby project maintained only by yourself) versus something actively maintained by a whole group of people. The experiment really should be done with some peers - if you can find willing victims (I mean, volunteers)! To me part of a language's utility is measured by how well it works in the group setting. That's what typically drives the industry, and more to the point for me it is the situation I will encounter most of the time.
- umvi 3y agoIt's an intentional design decision of go. In C#/Java, exceptions are often (ab)used to create invisible control flows through the program that are extremely difficult to follow since exceptions can float up arbitrarily large stacks before being intercepted and handled. In go since you are forced to handle every error on the current level of the stack you are on, there are no invisible control flows. Either you pass the error one level up the stack, or you don't.
- ubertaco 3y ago>In C#/Java, exceptions are often (ab)used to create invisible control flows through the program that are extremely difficult to follow I hear this often from Go advocates, but as someone who cut my teeth in Java in 2004, I've only ever seen this done in one codebase: an old streaming parser that I haven't seen since, and where the alternatives were, at the time, generally _more_ confusing than tossing an "unexpected end of input" exception that contained context. In the 19 years since (14 of which have been my professional career, mostly with Java as part of the job _somewhere_), I've never seen it since. In the meantime, though, I've run into better, more-explicit error-handling strategies (monadic errors, union types with explicit unwrapping) that manage to make the usually-bad option ("I'm swallowing this error") explicit and intentional, which is not something Go manages to achieve. (Interestingly, these better approaches all pre-date Go, so it's not like there wasn't a better state of the art to learn from.) But Go also doesn't make error-propagation easy either. Somehow, Go manages to optimize for accidentally swallowing errors, which is sort of impressively bad. It reminds me of something like INTERCAL: engineered to be as much a footgun as possible. INTERCAL is a parody, though, so it has that going for it.
- slantedview 3y ago> Somehow, Go manages to optimize for accidentally swallowing errors How so?
- josephcsible 3y agoBecause if you forget to write the code to account for an error, your program still compiles (unlike languages that use sum types for errors) and then said error will be accidentally swallowed when it occurs (unlike languages that use exceptions for errors). The only other languages I can think of that are as bad as Go about this are C and assembly.
- masklinn 3y ago> Whatever you do, always check your errors! Would probably help if you did not need an external linter to remind you. Alas, here as well Go is all hat.
- Animats 3y ago"All hat and no cattle", a Texas insult?
- cratermoon 3y ago> if you did not need an external linter to remind you That's true if the function doesn't return a value (other than the error) or if it does and the caller also ignores the return value. That is, given func F() error { ... } It's legal to call F() without checking the return. However in the case that the function returns a value and an error, like so func G() (int, error) { ... } then it's fine to just call G() and ignore anything returned, but i := G() will cause a compile error. It's possible to assign one or both values to the Blank Identifier, the underscore _, so ignoring the error, while possible, requires the code to reflect the intent, like so i, _ := G() but i, err := G() will fail to compile if either i or err are not used following the call. https://go.dev/play/p/Z8xNWiJHPV0 https://go.dev/play/p/Z8xNWiJHPV0 Maybe the compiler should require the error return to be assigned for F(), that's a bit of a quirk that's under discussion.
- masklinn 3y ago> That's true if the function doesn't return a value (other than the error) or if it does and the caller also ignores the return value. “Yes” would have done just fine, especially as this is not uncommon when it comes to IO. But then again who’d do IO in Go right? > will fail to compile if either i or err are not used following the call. a, err := G() if err != nil { panic(err) } i, err := G() i, err = G() err = F() fmt.Println("Got", a, i) > Maybe the compiler should require the error return to be assigned for F(), that's a bit of a quirk that's under discussion. It’s not “a bit of a quirk”. The langage only checks for unused variables which is woefully insufficient and unfit for purpose, and has been since the langage was first released.
- bananapub 3y agoyes, ted's code automates checking your errors, which is something Go itself neglected to do or even remind you the programmer to do
- badrequest 3y agoIt also prevents adding relevant context to the errors, meaning you get all the joy of hunting through call stacks to figure out what went wrong in a sufficiently complex program.
- t0astbread 3y agoWhen I write tooling for myself in Go, I usually wrap all of my errors with stacktraces since I hate having to grep for a string that may be interpolated or duplicated throughout the codebase. Addendum: It is still a good thing to be able to add context to the error message though, so I think you've got a point.
- HL33tibCe7 3y agoPart of me agrees with you. But part of me also sees the Go's team point on this, which is that not all functions always need their error checked - as an obvious example, fmt.Println. Sure, the compiler could add explicit exceptions for those cases, but that's a very unclean solution and it doesn't handle third party libraries. The "Go way" is to use the errcheck tool to do that. And on balance I think that having that defined in a separate tool - which can be configured by the user to exclude modules of their choice, and comes with good defaults - is the correct choice.
- TwentyPosts 3y ago>not all functions always need their error checked - as an obvious example, fmt.Println. I agree, but a lot of other languages handle this much better. In Rust (predictably!) you receive a Result type, and if you don't want to check the error you either .unwrap() it or apply the ? operator to pass it up. I feel like making "let's not check this error" explicit rather than implicit would be an improvement. Currently in Go it's impossible to tell whether someone forgot to check an error, or if they omitted the check intentionally. As an aside: I feel like if you are ever in a situation in which fmt.Println returns an error, then whatever situation you are in is already far, far beyond saving. Maybe fmt.Println should just panic on an error, that seems better than output silently being dropped!