How to make Go panic
In my past and current journey using the quite enjoyable Go programming language, there is one thing that bite me and stuck as an unhappy experience. The fact that you cannot always trust the == nil idiom.
Go fmt/print.go source code reads.
// If it's a nil pointer, just say "<nil>". The likeliest causes
// are a Stringer that fails to guard against nil or a nil
// pointer for a value receiver, and in either case, "<nil>" is
// a nice result.if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() {
Which is a workaround to recover from a situation where the runtime panics. Why is this that complicated when we keep writing if err != nil again and again?
Comparing to nil is not bulletproof
(*string)(nil) == nil // true
(interface{})(nil) == nil // true
The comment mentions Stringer, but we can use the error interface to make the == nil test fail.
type Bar struct {}func (Bar) Error() string {
return "bar"
}
This minimal struct implements the Stringer interface, hence gives a string representation of itself.
(*Bar)(nil) == nil // true
(error)(nil) == nil // true
(error)((*Bar)(nil)) == nil // false
Wait! What? A pointer to a Bar is detected as being nil but not once it’s been hidden away behind an interface.
Recovering
It’s probably not idiomatic to have checks involving reflection all over the place neither to do like the following example unless you’re sending rockets to space.
import (
"fmt"
"reflect"
)func Hello(e error) (err error) {
defer func() {
if r := recover(); r != nil {
if e == nil || reflect.ValueOf(e).IsNil() {
err = fmt.Errorf("hello: <nil>")
return
}
panic(r)
}
}()
err = fmt.Errorf("hello: %s", e.Error())
return
}
It’s probably not the nicest piece of code, you’ve ever seen, but it won’t fail you. https://play.golang.org/p/0x3b_YOwM1Z
Take away message
Interfaces are great, don’t get me wrong. But be wary that whenever you accepts a specific interface or the top type (interface{}) you can may panic on a nil value.
The type system cannot do everything is this case, so do check that what you get from the outside world matches what you expect. Also avoid sending nil pointers around.
edit: Dave Cheney article Typed nils in Go 2 explains why this behavior exists.