
Understanding Go panic output - kiyanwang
https://joeshaw.org/understanding-go-panic-output/
======
obeattie
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
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

~~~
NateDad
My 7 years of C++ and 7 yesterday of C# and 5 years of go agree with you. Many
more nil pointer exceptions in the former and almost none in the later in
similar size (large) codebases.

~~~
golergka
Most of my experience is in C#, and NullReferenceException is, by far, the
most frequent exception that ever happens. Curious about if your experience
with it is different, and if it is, how.

~~~
NateDad
Not sure if you're talking to me. I was saying that NullReferenceExceptions
happen a lot more in C# than nil pointer panics in Go.

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

------
Aissen
Or, you could use panicparse:
[https://github.com/maruel/panicparse](https://github.com/maruel/panicparse)

------
Walkman
Maybe the go panic output could be a little bit more helpful.

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

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

~~~
joeshaw
> I think there are plans to make an official debugger it just hasn't been
> prioritized.

There was a nascent project inside the Go team a couple of years ago (called
ergo I think?) but I believe it was shelved in favor of Delve, which was much
further along.

It's probably safe at this point to consider Delve the _de facto_ standard Go
debugger.

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

~~~
amelius
Thanks for the explanation.

Writing that if-construct after every function call doesn't sound like a lot
of fun. In fact it sounds like work!

Honestly, I think I'll wait until Go has matured a bit, before I try it for a
side-project.

~~~
laumars
It's easy to criticise this from a visual glance but you have to remember that
proper error handling in any language is hard work and often produces ugly
code where you need to account for multiple edge cases - many of which aren't
serious enough to cause an exception but would still break the program's
logic. All Go does is group these conditions into a standard interface.

Furthermore, because Go does unify these conditions, I've found it effective
for handling a whole array of differing edge cases that I might have otherwise
overlooked. In that regard I've anecdotally found it more effective than most
of the habits you see in other languages (I can't say _all_ languages because
I haven't used every language out there, but I have developed in excess of a
dozen different programming languages over the last 30 years).

In short, in Go error handling becomes a first class citizen rather than a
byproduct you begrudgingly need to check for.

Also while it might not be to everyone's tastes, adding "if err != nil {"
really does add practically nothing to your overall dev time in real terms.

But I'm not here to advocate one method of error handling over another, nor to
try and convince anyone to use Go who might not have otherwise. Obviously you
can see I get a little frustrated when people judge it purely on face value
but that's because I have spent some time with the language and found it to be
surprisingly effective at delivering a stable product.

~~~
amelius
> Also while it might not be to everyone's tastes, adding "if err != nil {"
> really does add practically nothing to your overall dev time in real terms.

But how do you compose functions then. You can't write:

    
    
        f(g(h(...)))
    

> when people judge it purely on face value

Yes, I can see that is frustrating. But I already know the style of
programming that requires if-statements instead of exceptions, as I have been
programming in C a lot, and I don't want to go back to that style because I'm
lazy and I'm sure I will forget to handle some errors. I want my language to
deal with this. Honestly, is that really too much to ask? Even javascript has
exceptions.

~~~
dullgiulio
> because I'm lazy and I'm sure I will forget to handle some errors

I really think you should try Go before being so convinced about your
assertions.

It's not easy to forget error handling in Go.

And letting exceptions bubble up all the way will improve things how?

~~~
erik_seaberg
A stack of ten functions doesn't need to explicitly catch and re-raise the
exception ten times to get it to where it can be handled.

~~~
NateDad
Sure, and then you have no idea what was executed and what failed. Did you run
9 functions or 1? What do you need to roll back? How do you handle the error?

This is the problem with exceptions. They make it too easy to be lazy with
your error handling.... Most of the time it's just catch and log, because the
code itself has no way of knowing what failed and what succeeded. This is how
you get your program in a bad state.... Because maybe you uploaded the file
but didn't set the metadata in the API, because the connection broke between
those steps.

With go's error handling it forces you to think about "what happens if the
code fails _here_ ". Its always obvious what code had been executed and what
has not.

To get that behavior in exception oriented languages, you'd have to wrap every
call in try/catch, which ends up just as verbose as go, if not worse.

~~~
masklinn
> Sure, and then you have no idea what was executed and what failed. Did you
> run 9 functions or 1? What do you need to roll back? How do you handle the
> error?

> […]

> With go's error handling it forces you to think about "what happens if the
> code fails here".

Did it run 9 functions or just 1? What do you need to roll back? How do you
handle the error?

The inner code could have just bubbled an error by hand from a deeper call (or
the library could even have used panic/recover internally to do so, the stdlib
used to do that). You have no more idea than in the exceptions-based code
unless you have the source available right there, which you'd then also have
in an exceptions-based language.

> To get that behavior in exception oriented languages, you'd have to wrap
> every call in try/catch, which ends up just as verbose as go, if not worse.

So your argument in support of Go's error handling is that the _very worst
case_ of exceptions-based error handling you can imagine is about as bad as
the _baseline case_ of Go's?

