Why Golang Nil Is Not Always Nil? Nil Explained

Newcoming developers starting their adventure with Golang are suprised by differencies that Golang has. One of them is the nil value...

Many times developers writing Golang code uses error value check whether the error was raised. It could look like this:

 
func do() error {
    return nil
}

func main(){
    if do() != nil {
        return err
    }
}
                        

These constructs are idiomatic to Golang, as it encourages developers to explicitly return errors as values and handle them as usual variable. But this post is not about errors, its about nil values.

Nil represents a zero value in Golang.

As you may already know, zero values are the "default" value for defined variables in Go. This means that some types doesn't cannot hold empty values, instead acquiring zero values upon initialization to get rid of the problem of checking values for emptyness. This rule translates into behaviour descirbed below, defining a variable initializes it with empty value.


var i int // 0
var t bool // false

type T struct {
    v string
}

var t T // {""}
                        

This behaviour applies to all types in Go besides pointers, slices, maps, channels, functions, interfaces.


var s []int             // s == nil -> true
var m map[string]string // m == nil -> true
var p *int              // p == nil -> true
var c chan int          // c == nil -> true
var f func()            // f == nil -> true

type T struct {
    p *int
}
var t T             // t.p == nil -> true

var i interface{}   // i == nil -> true

var a = nil         // compile error: use of untyped nil

                        

So far so good, there is no confusion here. Just to point out, I'm defining an i variable which will hold a value that implements empty interface, which is satisfied by all types in Go. I could have used any other interface with the same effect (nil upon initialization). The last example shows that nil is not a type therefore it cannot be used to be assigned to a value without a type.

The confusion comes when one want to use a construct like this:



var p *int
var i interface{}

i = p

if i != nil {
    fmt.Println("not a nil")
}

//the code outputs "not a nil"
                        

What happened here? We can clearly see (and based on previous rules) that both p and i has value nil but the condition is evaluated into false. This is not a bug it is a legitimate Golang behaviour.

What you should know is that interface is the reason of all the mess. The structure behind Golang interfaces holds two values - type and value. The type being a conrete type behind an interface (if any is assigned) and the type's value. To illustrate let's take a look at annotated examples that will help you understand Golang interfaces.



var p *int              // (type=*int,value=nil)
var i interface{}       // (type=nil,value=nil)

if i != p {             // (type=*int,value=nil) != (type=nil,value=nil)
// to successfully compare these values, both type and value must match
    fmt.Println("not a nil")
}

//the code outputs "not a nil"
                        

Another thing to consider is that hardcoded nil is always behaving like unassigned interface which is - (type=nil, value=nil). This can translate into another "weird" bahaviour, but once you understand the mechanics behind it you will be able to use it effectively.



var p *int              // (type=*int,value=nil)
var i interface{}       // (type=nil,value=nil)

if i != nil {           // (type=nil,value=nil) != (type=nil,value=nil)
    fmt.Println("not a nil")
}

i = p                   // assign p to i

// a hardcoded nil is always nil,nil (type,value)
if i != nil {           // (type=*int,value=nil) != (type=nil,value=nil)
    fmt.Println("not a nil")
}

//the code outputs "not a nil" only once
                        

The above example is the most confusing and may lead unexpected program behaviour since i variable can be passed along to another function which takes interface{} as input type parameter, therefore checking it only for basic i == nil will not suffice. What is the fix for that?

There are two solutions, one is to compare the value with typed nil value and the second one is using reflection package. Take a look at the example:



import "reflect"
import "unsafe"

func do(v interface{}){
    
    if v == nil {
        // this may not always work
        // if the value is (type=*int,value=nil)
        // the condition will not trigger
        // because hardcoded `nil` is (type=nil,value=nil)
    }

    if v == (*int)(nil){
        // one solution is to compare this value
        // with concrete type that has zero (nil) value
        // but this may lead to code duplication
        // and very hard to read logic if you want
        // to check multiple cases
    }

    if reflect.ValueOf(v).IsNil() {
        // https://golang.org/pkg/reflect/#Value.IsNil
        // by using `reflect` package we can check
        // whether a value is nil but only if it is one
        // of those 5 types that can be nil.
        // So be careful using it (it panics)
    }

    if (*[2]uintptr)(unsafe.Pointer(&v))[1] == 0 {
        // there is also more unsafe way
        // it checks the value part of an interface
        // directly for zero
        // it also doesn't panic if the value
        // is not one of 5 `nil able` types
    }

}

                        

Summing this up, the most confusing part is where you are trying to check whether the interface holds empty value (pointer, slice, function, interface, channel, map). In that case you must remember that you cannot safely rely on v == nil comparison. On the other hand why is it that you can safely compare those types against handwritten nil like the example below and get accurate results?


var i *int
fmt.Println(i == nil) // prints `true`
                        

In the example above the compiler does not the conrete type and can help you type the written nil so basically this translates to compiler doing i == (*int)(nil). With interface the compiler is not sure about the underlying type because it can change any time (you can assign string and then pointer to the same interface variable) leading to really unexpected program behaviour.

I hope I've explained all the mystics about nil value in Golang and now you know about every edge corner that you should cover in your project.

Benchmarks

Just for the closure, here are three benchmarks that shows the performance impact when using different nil checking strategies, I've used simple counter incrementation to be sure that the condition is actually done by the runtime (the compiler may cut the if statement if it cannot prove it has any effect on the program behaviour)


package main

import (
    "reflect"
    "testing"
    "unsafe"
)

func BenchmarkSimple(b *testing.B) {
    a := 0
    for i := 0; i <= b.N; i++ {
        var v interface{}
        v = (*int)(nil)
        if v == nil {
            a++
        } else {
            a++
        }
    }
}
func BenchmarkNilPointer(b *testing.B) {
    a := 0
    for i := 0; i <= b.N; i++ {
        var v interface{}
        v = (*int)(nil)
        if v == (*int)(nil) {
            a++
        } else {
            a++
        }
    }
}
func BenchmarkReflect(b *testing.B) {
    a := 0
    for i := 0; i <= b.N; i++ {
        var v interface{}
        v = (*int)(nil)
        if v == reflect.ValueOf(v).IsNil() {
            a++
        } else {
            a++
        }
    }
}
func BenchmarkUnsafe(b *testing.B) {
    a := 0
    for i := 0; i <= b.N; i++ {
        var v interface{}
        v = (*[2]uintptr)(unsafe.Pointer(&v))[1]
        if v == (*int)(nil) {
            a++
        } else {
            a++
        }
    }
}
                        

The results:


goos: linux
goarch: amd64
BenchmarkSimple         1000000000           0.270 ns/op
BenchmarkNilPointer     1000000000           0.277 ns/op
BenchmarkReflect        484543268            2.44 ns/op
BenchmarkUnsafe         1000000000           1.06 ns/op

                        

As can we see there is about 10 times bigger latency when comparing to nil using reflect package and about 3 times when it comes to comparison using unsafe package.

From my experience the above nil checking gotchas doesn't occur very often because mostly we are dealing with empty interfaces in our code. The most common case might appear when developer are trying to return error values of their kind from a function that does not get assigned a value (only initialized). Take a look at a last example.


// example #1
type myerr string

func (err myerr) Error() string {
    return "an error ocurred: " + err
}

func do() error {
    var err *myerr
    //do logic...
    return err // might return nil pointer
}

func main() {
    err := do()
    print(err == nil) // prints `false` because it is nil pointer
}

// example #2
type myerr string

func (err myerr) Error() string {
    return "an error ocurred"
}

func do() *myerr {
    return nil // returns nil pointer
}

func wrap() error {
    return do() // the information about nil pointer is dissolved
}

func main() {
    err := wrap()
    print(err == nil) //prints `false` because underneath is nil pointer not empty interface
}

                        

The solution is simple - always use error interface when returning an error from function and never initialize an empty error variable that might be return from function (as `nil`).This makes the code more readable and generic but also avoid the situations above.

Thank you for your time spent reading the article and I hope it resolved some of the issues about nil checking in Golang.

Code Fibers provides services for Golang development and consulting, I invite you to visit our website and contact also get to know how we work and what is our exprience.

These posts might be interesting for you:

  1. Why appending to slice in Golang is dangerous? Common Slice Gotchas
Author: Peter

I'm a backend programmer for over 10 years now, have hands on experience with Golang and Node.js as well as other technologies, DevOps and Architecture. I share my thoughts and knowledge on this blog.