Nillability and zero-values in go

Beeing a long time java-developer, I am obsessed with null-checking and handling null values. In golang, the story is somewhat different. In this post I will try to describe how nil and zero-values are used in golang.

non-nillable and nillable types

Types can be either nillable or non-nillable in go. The non-nillable types can never be nil and will never cause you a nil-panic (the java equivalent of nullpointerexception) But when are dealing with the nillable types, we have to take a bit of caution although not as much as in java(or other languages with nillable types).

The non-nillables basic types

In go, the basic types are not nillable. A statement like

var a int = nil

does not compile because an int can never be nil. The default value of an unassigned int type is 0. Running the statement

var a int // default value of int, cannot be nil
fmt.Println(a) // 0

will output the default value of int; “0”. We call this the zero-value of the type.

The same way int defaults to 0, these are the other basic types with their zero-values:

Types Zero value
int, int8, int16, int32, int64 0
uint, uint8, uint16, uint32, uint64 0
uintptr 0
float32, float64 0.0
byte 0
rune 0
string ”” (empty string)
complex64, complex128 (0,0i)
arrays of non-nillable types array of zero-values
arrays of nillable types array of nil-values

Non-nillable structs

Composed struct types are also non-nillable, and the default value of a struct will contain the default value for all its fields.

Consider the code with the struct type Person,

type Person struct {
    Name string
    Age  int
}
var p Person // zero-value of type Person

fmt.Printf("[%#v]\n", p)

will print [main.Person{Name:"", Age:0}] when run in main. You can test this on this snippet on The Go Playground.

The nillable types

More advanced types are nillable and can cause panic if they are not initialized.

The nillable types are functions, channels, slices, maps, interface-types and pointers.

However, nil-slices and nil-maps can still be used and does not have to be initialized before we start using them.

nil-maps

Maps will always return the zero-value of the value if it is nil, the same behaviour as if the key of the map is non-existent. The code

var p map[int]string // nil map
fmt.Printf(" %#v  length %d \n",  p[99], len(p))

simply prints "" length 0, the extracted value for key 99 is the zero value of string.

Assigning values to a nil-map, will however cause panic:

var p map[string]int    // nil map 
p["nils"] = 19 // panic: assignment to entry in nil map

nil-slices

Refering to out-of-bounds on slices will cause panic, but operations like len() and cap() will not panic. They will simply return 0, since both capacity and length is zero for an uninitialized slice. Append can be safely called on the nil-slice. So the code

var p []string // nil slice
fmt.Printf("uninitialized -> %d, %d\n",  len(p), cap(p))
p1 := append(p, "nils") // create a new slice p1 from p
fmt.Printf("after append  -> %d, %d %#v\n",  len(p1), cap(p1), p1)

Will print

uninitialized -> 0, 0
after append  -> 1, 1 []string{"nils"}

Play with this example on the playground.

nillable pointers, functions and interface-types can cause panic

Pointers and interface-types are however nillable. Whenever dealing with these types, we have to consider if they are nil or not to avoid panics. These code-snippets for instance, will cause a panic:

var p *int // pointer to an int
*p++ // panic: runtime error: invalid memory address or nil pointer dereference
// p is an address to nothing, and therefore is nil

and

var p error // nil-value of type error
error.Error() // panic: runtime error: invalid memory address or nil pointer dereference

and

var f func(string) // nil-function
f("oh oh") // panic: runtime error: invalid memory address or nil pointer dereference

nil channels blocks forever

Trying to read from a nil-channel or write to a nil-channel will block forever. Closing a nil-channel will cause panic.

Wrapup

nil is well defined in go. Knowing what can be nil and how to handle nil values of the different types increases your understanding of whats happening and can help you write better go-code.