Why Go Can't Try
10 points by telemachus
10 points by telemachus
This is LLM written, gets at least a couple minor things wrong, gets the main thesis ambiguously wrong, and buries the lede in an annoying way.
At first glance it seems simple. try isn't a keyword in Go today, so adding it wouldn't break existing code.
It would break any existing code that uses try as a variable name. To add a new keyword without breaking existing code, it needs to be reserved by the language for future use, so that it's an error to use it as a variable name. Whether Go does this, who knows, because no one looked it up, because the article was written by an LLM.
They've historically drawn a line between this and exceptions, arguing both make control flow hard to follow.
That's the opposite of what drawing a line between two things means.
The thesis is that Go can't just add a try keyword to reduce boilerplate. The article says this directly and indirectly in a few places. But Go could add try; there's a simple and fully fleshed out proposal for it:
https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md
Here's the actual lede which is buried:
Here's the uncomfortable truth: a
trykeyword in Go without fixing the error type is just syntax sugar. You'd get slightly less typing but none of the real benefits - no exhaustiveness checking, no compiler-inferred error sets, no guarantee that you've actually handled every case. It would make control flow less visible while delivering only a fraction of what makes Zig's approach genuinely good.
Yes, that's correct: adding a try keyword (i) would mean that when you see try, that's control flow that flows to the end of the function just like return, and (ii) would not magically allow Go to start doing exhaustiveness checking on error messages the way that Zig does.
Marked as spam. We really need a "slop" tag. I don't even care if something is LLM written, but "looks good on the surface, actually has lots of holes" is really common now, and there really ought to be a way to mark it.
I flagged it too. It made no sense to me. I don’t understand why it’s necessary to conflate syntactic sugar with error enumeration - these problems seem orthogonal to me (and, as a reformed Java developer, please Go never make us enumerate errors)
On the flip side, it says that it’s not possible to add a stack trace - again, unrelated to the initial premise - but since Go errors are in a tree, it would be trivial to insert a new error containing a stack trace as part of the try (I’m not saying this is a good idea, just that it’s possible)
I am not an LLM hater but I am starting to become very annoyed with the people who push this slop onto us.
This is good analysis, I've been thinking for a while that people are wrong about Go's error handling. They correctly intuit that it's bad, but focusing on the if err != nil pattern is misplaced; it's the type system that's the real problem.
Another issue not discussed here are the error returns from functions. They are written like a tuple, but the language can't express that in a type, so you are forced to handle it manually.
The if err != nil pattern is the user-visible part of the problem. Focusing on user-visible problems is entirely correctly placed.
I am undoubtedly missing something wildly obvious but isn't the Zig "where the error traveled" error report less powerful in dynamic situations (and let's face it, that's mostly why we write code) than Go? e.g. if I'm loading a bunch of things via something like
for _, v := range interestingFiles {
err := parseFile(v)
if err != nil {
return fmt.Errorf("parse failed: %s: %w", v, err)
}
}
Go's error return can tell me which file failed (albeit inside an error string - I concede that error having a context would be helpful for attaching random bits and bobs) but Zig's try reporting - again I am probably missing something obvious - cannot be useful because "the [error.FileNotFound] value alone can't tell you which file was missing" and "an unhandled error points you directly to the failing try" will point to the same line whatever file failed, right? With Zig, you'd have to be printing out the filenames (perhaps at a debug log level) in order to know which one failed, right? You can't even do any run-time shenanigans (like "create a transient wrapper method for each filename and get the erroring filename from that") because Zig seems to be explicitly "everything is nailed down at compile time".
(Go could introduce more structured types if they wanted - I don't think there's anything in the if err != nil meta that prevents it if you really want to - see https://go.dev/play/p/t2YT6QWrxMJ as a sketchy demo. Having to know the specifics of the error type you get back to deconstruct it via type assertions is a bit faffy but eh, it was a 10 minute hack job. But again I may well have missed something blindingly obvious somewhere.)