Nice article, thanks for publishing this. However:
>This panic is caused by dereferencing a nil pointer, as indicated by the first line of the output. These types of errors are much less common in Go than in other languages like C or Java thanks to Go’s idioms around error handling.
I often see assertions made like this about Go. My experience at companies which use Go as their primary development language with reasonably-sized teams (30-50 people working on hundreds/thousands of Go programs) does not agree with this. I would say nil pointer dereference errors are just as common as if they were writing code in C or Java. I don't have hard data to back this up though; perhaps you do.
> If a function could fail, the function must return an error as its last return value. The caller should immediately check for errors from that function.
In practice, in idiomatic code it is quite rare for a function that returns a `(* Something, error)` to return `(nil, nil)`, which are the cases where a nil pointer dereference would most often happen. The other case is not _immediately_ checking errors. I would say both of these cases are not idiomatic Go. (Yes, there are exceptions.)
The other cases where I've seen nil dereferences pop up most often:
(1) the zero value for maps is nil, and a nil map is mostly not usable. `m[50] = "foo"` will panic with a nil dereference if you've not initialized `m`. This is one of the most annoying things about Go.
(2) not checking for presence when pulling out of a map. If you have `map[string]* Something` and do `x := m["nonexistent"]` and try to use `x`, it'll blow up. Always use the `x, ok := m["nonexistent"]` form instead, and deal with stuff when `ok == false`.
(3) nil receivers. `var x *Something`, then `x.Method()` -- the `Method()` method _can_ deal when `x == nil`, but most code does not.
My reasons for citing C and Java:
C doesn't have multiple return values, and kinds of C code deals with errors differently. Lots of stuff just returns `NULL` and all too often things don't check for it. (`malloc()` is a perfect example of this -- it rarely fails but a _lot_ of code doesn't check for errors. Now extrapolate that to everything :)
For Java, it's because the try-catch control structure complicates program flow. If you have a large block of code in a try-catch, it is very easy for a different line of code to throw an exception than you expected, and as a result an object you attempt to dereference later is null and you get an NPE. There are ways to deal with this, of course, but in my experience most people are pretty lazy about the non-happy path.
EDIT: formatting. how can a site about programming have such terrible support for typing in code
One of the most common errors that i used to encounter when i first started with golang was- accessing a map without initializing it first. For example:
var m map[string]int
m["name"] = 23
which resulted in "panic: assignment to entry in nil map".
Nil pointer access errors are relatively easy to debug when in Go world, as compared to in CGO. Like, we are using zmq via CGO in one of our project and when these errors occured, we had to use gdb to debug (apart from logging). At one time we even used rr.
I really fail to see how "Go's idioms around error handling" would make NPE rarer than in Java either.
In java, an error is an exception and will bubble up to the handler, it has very little reason to "birth" null references.
In Go, an error is a non-nil value next to a zero (often nil) value, and a success is the reverse, so error handling always injects new nils which you're supposed to carefully not used (since no sum types).
My experience is that it's slightly less common in Go than in C or Java, but quite a lot easier to debug than either of the other two. My explanation for why it's less common in Go is that fewer things are pointers, especially compared to Java, and that the things that the cause is more immediately apparent, especially compared to C. Of course, nil/null is still an unnecessary problem in all of these languages, in the sense that there are language features that render these problems obsolete; however, these languages (especially C and Java) aren't likely to adopt these features or at least not to adopt them and completely sunset nil/null for compatibility reasons.
As a not-yet-experienced Go programmer, I have to say it'd make it way more palatable if the reference to the error in my code could be somehow extracted and displayed in a more obvious fashion. I've had panic happen in Go code before (most of the time when trying to read the body of a http response that happened to be nil for whatever reason) and I was kind of lost in a trace of errors happening deeper in the standard library that I mostly didn't care about at first sight (until a few lines down, I could find a reference to my own program).
That being said, Go is still a pretty good language when it comes to tooling and error handling / reporting.
To offer a counterargument, I've always considered Go's panics to be pretty readable because you can read the top line to get the error and then one of the next lines down (I forget which but after a while it tends to stand out) to see the line where the error was raised. The rest of the stack is if you need to trace the error back through the calling routines (I rarely need to do this but it's useful if the panic isn't being raised by your own code).
Granted things get a little more tricky when you start multiplying the number of concurrent goroutines. I've sometimes ended up with several terminal screens full text. But so long as you can find the start of the panic output then you can generally find what you want without too much difficulty.
Have you ever looked at typical template compiler errors in C++? The same problem occurs, I suppose, but then much worse. I regularly have multiple screens of text where your own single line of code is hidden like a needle in a haystack. Clang seems to do this _slightly_ better than gcc, but it's both bad.
On the whole I am very happy with Go, but this did bite me recently. We're using the X509 package which is returning an error on a handful of CA certificates due to characters that are not technically allowed in an ASN.1 PrintableString field (E.G `&`). Sure, fine, we've already forked the package so it's easy to make the change to be more lenient. Except errors don't contain any information about the origin of the error in source code, so it's been down to painful debugging and source code digging to find where this check is being made.
If anyone has a less painful way to do this, please halp.
Part of the reason has to do with Go's original primary use case: a server handling thousands of requests. In that environment a traditional debugger, at least in my experience, isn't super useful, because it models a single-stepped workflow but the program is massively concurrent. (also pausing a production app is probably a bad idea)
The kinds of bugs you run into are different and harder to track down, so Go has invested resources into some of those problems (for example the race detector, and automatic detection of concurrent map misuse) and you tend to rely on logging, profiling, tracing and stats to solve bugs.
I think there are plans to make an official debugger it just hasn't been prioritized.
Panics can be caught and handled by parent callers and thus behave a little bit like exceptions (not as pretty I'll grant you, but still better than the "no way out other than aborting the process" approach it seems at first glance). However you generally you don't want to catch panics except under rare conditions because panics are intended to be used in "the world is ending" kind of scenarios (it doesn't always work out this way but that is the theory).
For general error handling there is an error object that gets returned by functions. It leads to the often moaned about syntax below:
if err != nil {
return err
}
Yeah it's ugly. Yeah other languages have try / etc. But for all of it's sins I've found it still gets the job done.
[+] [-] obeattie|8 years ago|reply
>This panic is caused by dereferencing a nil pointer, as indicated by the first line of the output. These types of errors are much less common in Go than in other languages like C or Java thanks to Go’s idioms around error handling.
I often see assertions made like this about Go. My experience at companies which use Go as their primary development language with reasonably-sized teams (30-50 people working on hundreds/thousands of Go programs) does not agree with this. I would say nil pointer dereference errors are just as common as if they were writing code in C or Java. I don't have hard data to back this up though; perhaps you do.
[+] [-] joeshaw|8 years ago|reply
> If a function could fail, the function must return an error as its last return value. The caller should immediately check for errors from that function.
In practice, in idiomatic code it is quite rare for a function that returns a `(* Something, error)` to return `(nil, nil)`, which are the cases where a nil pointer dereference would most often happen. The other case is not _immediately_ checking errors. I would say both of these cases are not idiomatic Go. (Yes, there are exceptions.)
The other cases where I've seen nil dereferences pop up most often:
(1) the zero value for maps is nil, and a nil map is mostly not usable. `m[50] = "foo"` will panic with a nil dereference if you've not initialized `m`. This is one of the most annoying things about Go.
(2) not checking for presence when pulling out of a map. If you have `map[string]* Something` and do `x := m["nonexistent"]` and try to use `x`, it'll blow up. Always use the `x, ok := m["nonexistent"]` form instead, and deal with stuff when `ok == false`.
(3) nil receivers. `var x *Something`, then `x.Method()` -- the `Method()` method _can_ deal when `x == nil`, but most code does not.
My reasons for citing C and Java:
C doesn't have multiple return values, and kinds of C code deals with errors differently. Lots of stuff just returns `NULL` and all too often things don't check for it. (`malloc()` is a perfect example of this -- it rarely fails but a _lot_ of code doesn't check for errors. Now extrapolate that to everything :)
For Java, it's because the try-catch control structure complicates program flow. If you have a large block of code in a try-catch, it is very easy for a different line of code to throw an exception than you expected, and as a result an object you attempt to dereference later is null and you get an NPE. There are ways to deal with this, of course, but in my experience most people are pretty lazy about the non-happy path.
EDIT: formatting. how can a site about programming have such terrible support for typing in code
[+] [-] LaFolle|8 years ago|reply
Nil pointer access errors are relatively easy to debug when in Go world, as compared to in CGO. Like, we are using zmq via CGO in one of our project and when these errors occured, we had to use gdb to debug (apart from logging). At one time we even used rr.
rr is really cool, everyone should check this out. https://github.com/mozilla/rr
[+] [-] bsaul|8 years ago|reply
Go do makes things easier, but not fail proof.
[+] [-] masklinn|8 years ago|reply
In java, an error is an exception and will bubble up to the handler, it has very little reason to "birth" null references.
In Go, an error is a non-nil value next to a zero (often nil) value, and a success is the reverse, so error handling always injects new nils which you're supposed to carefully not used (since no sum types).
[+] [-] weberc2|8 years ago|reply
[+] [-] icholy|8 years ago|reply
[+] [-] VeejayRampay|8 years ago|reply
That being said, Go is still a pretty good language when it comes to tooling and error handling / reporting.
[+] [-] laumars|8 years ago|reply
Granted things get a little more tricky when you start multiplying the number of concurrent goroutines. I've sometimes ended up with several terminal screens full text. But so long as you can find the start of the panic output then you can generally find what you want without too much difficulty.
[+] [-] tomsmeding|8 years ago|reply
[+] [-] mellett68|8 years ago|reply
This is especially painful when you introduce frameworks and things that create so much misdirection that the call stack can be enormous.
[+] [-] Aissen|8 years ago|reply
[+] [-] Walkman|8 years ago|reply
[+] [-] 0xFFC|8 years ago|reply
P.S. I am familiar with delve. I am looking for official debugger support.
[+] [-] Nvorzula|8 years ago|reply
If anyone has a less painful way to do this, please halp.
[+] [-] cdoxsey|8 years ago|reply
The kinds of bugs you run into are different and harder to track down, so Go has invested resources into some of those problems (for example the race detector, and automatic detection of concurrent map misuse) and you tend to rely on logging, profiling, tracing and stats to solve bugs.
I think there are plans to make an official debugger it just hasn't been prioritized.
[+] [-] amelius|8 years ago|reply
"Panic" sounds like there's no way out other than aborting the process.
[+] [-] laumars|8 years ago|reply
For general error handling there is an error object that gets returned by functions. It leads to the often moaned about syntax below:
Yeah it's ugly. Yeah other languages have try / etc. But for all of it's sins I've found it still gets the job done.[+] [-] Waterluvian|8 years ago|reply
Maybe it's the snake handler in me but i feel that exception handling is a valid part of control flow.
[+] [-] cdoxsey|8 years ago|reply
[+] [-] randomdata|8 years ago|reply
[+] [-] mememaestro|8 years ago|reply
[deleted]