Go won't make error handling easier. Cursor says "just press tab"
Go closes the door on error syntax proposals, citing lack of consensus. Behind the scenes, LLMs quietly fill the gap.
Gopher logo license1
Programming note: If you don’t know a lot about Golang, check out the Appendix below for all the background you need.
Go recently announced that they will not pursue syntactic improvements for error checking. They managed to do this without addressing the elephant in the room, which is that LLM code generation has gotten to the point where syntactic sugar doesn’t matter as much as it used to.
[ On | No ] syntactic support for error handling
Still, no attempt to address error handling so far has gained sufficient traction. If we are honestly taking stock of where we are, we can only admit that we neither have a shared understanding of the problem, nor do we all agree that there is a problem in the first place. With this in mind, we are making the following pragmatic decision:
For the foreseeable future, the Go team will stop pursuing syntactic language changes for error handling. We will also close all open and incoming proposals that concern themselves primarily with the syntax of error handling, without further investigation.
There are also a few introspective questions that have not featured heavily in the Online Discourse, but are actually really important to hammer out.
We don’t really know how much the issue is the straightforward syntactic verbosity of error checking, versus the verbosity of good error handling: constructing errors that are a useful part of an API and meaningful to developers and end-users alike. This is something we’d like to study in greater depth.
This is a more important point that is lost in the discussion. Do errors fail to serve both end-users and developers? If you’ve used a lot of third-party libraries in Golang, you know that the answer is “lol yes.” Since Go errors are just types, you’re at the mercy of every single layer of your stack to thoughtfully provide and handle errors. This includes all of the layers that your dependencies transitively pull in. Nothing more fun to get a random error bubbling up from a library — stack trace not included — that says something like “config error” and you’re just screaming “What failed to initialize? Do I need to set another config option? Is something misspelled? What did you see?”
If you think to yourself, “why don’t they just do $myfavoritesolutiontothisproblem,” I highly recommend skimming the umbrella issue that Ian Lance Taylor assembled to gather more serious proposals into one place. Why do they need an umbrella issue? Because people keep coming up with the same proposals over and over again. They’ve been debated. They’re all missing something.
I’ve only been a professional Golang coder for 2 years, but I’ve been coding Golang since before the 1.0 release. It’s my favorite language for web servers.
I was hyped for the initial check
proposal. But now, with the advent of LLM tools, it doesn’t feel that pressing.
Go is a great language for code generation. I’m not the only one who has noticed this. LLMs are excellent at automatically providing the correct error handling for the context. Do you just pass them through? Just press Tab. Do you wrap them? Just press tab.
So now you get the best of all worlds: the error handling is out in the open, and the LLM generates all of the checks. Very little effort is actually expended “writing” the code. In a world where it takes the same amount of effort to type ? as it does to type 4 lines of error handling, do you really want to waste your time with syntactic proposals?
I did wonder briefly why the Go team didn’t mention this. But pretend that you’re designing a language. It would actually be weird if you did mention it! You’re just desining a language. The tooling that exists to generate the language might not exist in 5 years. What if everyone decided that AI was a money-losing investment and LLM code generation tools started being charged at cost instead of subsidized to win marketshare? That puts you in an awkward position as a language designer. You designed your language with the assumption that tooling would go in a specific direction, and now it’s going in another direction.
So obviously, you just put out a blog post that says “we’re not going to talk about syntactic changes anymore.”
Appendix: background for non-Go programmers
Are you a programmer that doesn’t know Golang? Here’s all the context that you need!
Go has two different types of errors: panics and errors.
panic
is an unchecked exception. It behaves how you might expect: the stack is automatically unwound until either the panic is handled or it reaches the top level and the application is terminated. To give webservers as an example, you might wrap all of your HTTP handlers in a function that recovers from the panic, logs the error associated with the panic, and then returns a “500: Internal Server Error” to the request.
Go intends these to be reserved for truly exceptional events, like dereferencing nil pointers.
Nothing stops you from using these as exceptions in your program. However, if you panic()
for a non-fatal error, the Go community will call your code “not idiomatic.” When the Go community says “not idiomatic,” they are punishing you. This is meant to be on par with excommunication from your church, or being left alone naked on a desert island. All of this is to say: you’re only supposed to panic for truly fatal problems.
The other type of error return is the error
interface. This is an extra parameter that is returned from every function. By convention, it is the last return type. These are just types that implement an interface and don’t have any special handling. There are even some fun “gotcha!”s around Go’s nil interface semantics.
By design, Go’s errors are intended to be checked with manual if checks. There is no linguistic support for this; just use the regular control flow structures available in the language. In practice, almost every check just looks like this:
a, err := somepackage.SomeFunction()
if err != nil {
return nil, err
}
Of course, the handling can get arbitrarily complex. For example, you might examine the error type to determine whether an I/O error is fatal or maybe the request can be retried. Or you might return a wrapped error to accumulate a stack trace for yourself2.
In theory you could forget to check the errors, but there are linters that make you.
So, dear programmer that doesn’t know Go, the question facing the community is: “should errors in Go be handled like they were normal types, or should errors have extra language support?”
The Go gopher was designed by Renee French.
The design is licensed under the Creative Commons 4.0 Attributions license.
Errors don’t have stack traces by default in Go. Yes, sometimes it’s a problem.