top | item 15719853

Understanding Go panic output

99 points| kiyanwang | 8 years ago |joeshaw.org | reply

58 comments

order
[+] obeattie|8 years ago|reply
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.

[+] joeshaw|8 years ago|reply
My explanation for why is the following sentence:

> 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
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.

rr is really cool, everyone should check this out. https://github.com/mozilla/rr

[+] bsaul|8 years ago|reply
Not a lot of experience in go, but I’d say deadlocks ( yes, even with channels), and nil pointers are indeed the two most common errors i encountered.

Go do makes things easier, but not fail proof.

[+] masklinn|8 years ago|reply
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).

[+] weberc2|8 years ago|reply
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.
[+] icholy|8 years ago|reply
It happens way more often in java because all references are nullable. In Go, unless you're dealing with a pointer, it's not going to be nil.
[+] VeejayRampay|8 years ago|reply
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.

[+] laumars|8 years ago|reply
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.

[+] tomsmeding|8 years ago|reply
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.
[+] mellett68|8 years ago|reply
Really this is my main complaint with most languages/tools when it comes to runtime errors.

This is especially painful when you introduce frameworks and things that create so much misdirection that the call stack can be enormous.

[+] Walkman|8 years ago|reply
Maybe the go panic output could be a little bit more helpful.
[+] 0xFFC|8 years ago|reply
Sorry for this unrelated question. I am not very familiar with Go ecosystems. I have one question why there is not official debugger for Go?

P.S. I am familiar with delve. I am looking for official debugger support.

[+] Nvorzula|8 years ago|reply
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.

[+] cdoxsey|8 years ago|reply
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.

[+] amelius|8 years ago|reply
Not a gopher, and wondering why there are no proper exceptions.

"Panic" sounds like there's no way out other than aborting the process.

[+] laumars|8 years ago|reply
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.
[+] Waterluvian|8 years ago|reply
The original design was no exceptions ever. Then panic was added in later for last resort cleanup. At least that's my understanding.

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
Basically because most "Exceptions" aren't very exceptional so errors should be handled as an ordinary part of your control flow.
[+] randomdata|8 years ago|reply
Arguably, Go has exceptions. It lacks "normals" like you find in other languages. You can recover panics with the recover function.