There’s been a debate raging in some corners of the internet lately about how superior Go‘s error handling is to other languages. Â I am going to address some of the points made, here:
Claim 1: It’s impossible to ignore errors in Go, they are “in your face”
This is patently false. Â Take this example:
fmt.Println("Hello world")
Pretty innocuous wouldn’t you say? Â Well let’s take a look at the language documentation for fmt.Println:
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error)
So Println can return an error! Â Where did we check it? Â Well, we didn’t. Â Any other claims that it’s OK to ignore it in this case further strengthen my argument.
Some will say that it’s a deliberate choice to ignore the error and I deserve all I get. Well, was it? I didn’t even know that Println returned an error until I looked at the documentation (and who is going to do that for Println?). And that’s the point, if I need to look at the documentation to see that it can return an error, then if I am using a language that raises exceptions I will have also seen its documentation about how it deals with errors.
You could even argue that an exception is superior in this case. Â With Go, the code will march on regardless, oblivious to the fact that Println failed. Â With exceptions, it’ll fail and show you exactly where it failed.
The language will error at compile time if you try to ignore an error returned as a second value and you only take the first.  But this is trivially bypassed by assigning it to _, which when reading code is easily missed compared to the exception style of “catching then dropping”, because Go itself encourages this style of assigning to _ with its own range statement as a deliberate way of ignoring things that the language is trying to force you to see.
So really in both cases, ignoring the error doesn’t really stand out as wrong.
Here’s a concrete example in Go I was recently shown:
w := bufio.NewWriter(os.Stdout)
for _, name := range ListAll(conf) {
  fmt.Fprintln(w, name)
}
w.Flush()
return
As you can see, the caller completely forgot to check the error returned from Fprintln and Flush and there would be no compiler warning about it.
Claim 2: Exceptions teach developers to not care about errors
Citing an example where someone didn’t catch an exception and the code consequently blew up is really not a good example of this claim. Â It’s a bug, for sure and you get a full traceback of your error in the resulting exception, which is handy. Â You go away and fix it quickly based on that info.
If I am in the same situation with Go and I ignore a returned error from a function, at some point (which is likely to be nowhere near the place where the error occurred) my code will blow up.  I’ll have to run up the debugger to try and find out where it really occurred though.
Because unused variables in Go are a compile-time error, it’s actively discouraging you from assigning the result of the function to a variable (or you can deal with it, of course). Â For anyone who’s not read the full documentation for a function call or missed its return value (we’re all human) as I said above – you’re not even going to notice that you missed it.
Based on this, I can see no difference at all that suggests one way or the other teaches developers to not care about errors. Â Developers do care about errors, really, but bugs creep in however careful you are. Â And when they do, I’d rather have a decent indication of where the bug is.
Other parts of error handling that I dislike
When you look at the average Go program, you will see a lot of this:
if err != nil {
 return nil, err
}Â
This is the recommended way of error handling in in Go.  But this is not error handling, it’s error propagation.  In nearly all languages there will arise situations where in well-factored code you have a low-level error that you need to pass right back up to the entry point for the caller. That means you need this error propagation code in every single place where you check for errors.  There’s no syntactic sugar, just the same three lines everywhere.
For me, this vastly decreases the readability of the code. This is where exceptions excel because inside my own library I can factor the bejeesus out of it into many small functions and if I need to return an error, I just catch a lower-level exception in the top-level function and return something else. Â You can do this in Go with a panic(), but it seems to be discouraged. Â Panic() feels almost exactly like using exceptions, only the syntax is worse. If Go’s style is to encourage people to handle errors like this, it needs the sugar.
Conclusion
Many people might think that I completely hate Go’s error handling from this post. Â That’s not strictly true – I don’t hate it, I just think it can be improved. Â I challenge assumptions that I see which state that Go’s error handling is superior in some way, when as far as I can see it’s not that different from other languages in terms of usefulness.
Go is clearly in its infancy. Â Most languages will have started out with youthful enthusiasm and realised that some change was needed. Â These languages are the successful ones where developers enjoy coding in it and feel productive. Â I hope that Go embraces change as it matures and attracts more developers.
I welcome comments on this post – unlike some people I won’t censor them or delete ones I can’t argue with (unless they are outright abusive and use foul language, this is a family blog!).