Understanding Go panic output
joeshaw.org
joeshaw.org
>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.
Go do makes things easier, but not fail proof.
Though regardless, after using Rust for a while, i hope we can find a way to bring that safety to Go. I use a compiler and not JS/Python/etc for a reason, after all. I want compile time safety, all the time.
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
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).
> 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
That being said, Go is still a pretty good language when it comes to tooling and error handling / reporting.
This is especially painful when you introduce frameworks and things that create so much misdirection that the call stack can be enormous.
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.
P.S. I am familiar with delve. I am looking for official debugger support.
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.
I'm pretty sure the question was about debugger to be used during development.
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.
If anyone has a less painful way to do this, please halp.
"Panic" sounds like there's no way out other than aborting the process.
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.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.
If it is not mature enough for you now - it will never be. Right now it is already used in many huge IT companies and extremly popular in China. Writing that if-construct after every function is the design, not the lack of it.
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.
But how do you compose functions then. You can't write:
f(g(h(...)))
> when people judge it purely on face valueYes, 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.
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?
It's very easy to do so if the function has no useful return value or you don't care for it e.g. Writer.Write: https://play.golang.org/p/cPh0PJoz-v
> And letting exceptions bubble up all the way will improve things how?
Errors the developer forgets (or does not want) to handle will trigger a default handler.
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.
> […]
> 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?
Because errors are a returned interface you don't forget to handle them. In fact you have to explicitly tell the language not to handle them by using a _ token on the function call.
However it sounds like your argument is more personal preference regarding coding style (eg it seems you tend to favour Sexpression style of syntax over the C-style of syntax) and that's reasonable enough. I do write a lot of code like that too - albeit not in Go.
Not all functions have to return an error. You can come across something like func(x, y, func() { ... }) Pretty often.
Anyway, I'm not quite sure how is that different from, say, Ruby's begin...rescue.
Ruby lets you write foo(bar(baz())), the rescue block will just coalesce errors in any of the three calls.
And because Go has no generics and its errors are not reified (handled through MRV which is a special language construct) you can't build abstractions like flatmap/then/>>= which would let you compose these functions (or function calls), you have to handroll every instance.
Good to know (though somewhat disgusting), but for error-returning functions I would assume the external ones would take a single argument aka in foo(bar(baz()) I'd expect
func foo(arg W) (result X, err error)
func bar(arg V) (result W, err error)
func baz() (result V, err error)But imagine the consequences of a program that doesn't do this work. If it is doing something important, fingers crossed.
(Keep in mind: unhandled exceptions are like panics. Not good.)
Writing assembly instructions also sounds like work, and it is, but I'm glad my language/compiler does it for me ;)
Monadic error handling has all the supposed benefits the return value style errors but allows for easy composition. After years I'm still infuriated with error handling in golang and it was such an easy thing to avoid.
Strange, I've always heard the opposite: if you write a function that can panic, you always want to handle it in your code that calls the function (which should be the only code that could) because panics should generally not be exposed across a public interface, including the boundary between the top-level Go code to the outside world.
Of course you do not want to use panic in sensitive code. The best example is looking at how panic() is used in crypto/tls (which has a lot of sensitive code! You really don't want a client to be able to crash a server): https://gist.github.com/mimoo/93c33d88222c87fb384b4ca3b04fb5...
Further, it is considered good practice to recover from panics within library API boundaries, and return errors across the API instead. "The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values."[1]
The encoding/json package is interesting because it uses both panic() and recover() internally in a way more reminiscent to how exceptions are used in other languages (that is, for control flow).
For example, https://github.com/golang/go/blob/2596a0c075aeddec571cd658f7... panics, and https://github.com/golang/go/blob/2596a0c075aeddec571cd658f7... recovers it.
Both uses are quite uncommon, though.
Maybe it's the snake handler in me but i feel that exception handling is a valid part of control flow.