Golang: Nil vs Empty Slice

Habib Ridho
2 min readMay 8, 2019

--

Slice is one of the most common data type used in Golang. It enables us to contain contiguous data of any defined type which we can append to or iterate through.

var foo []string
foo = append(foo, "a")
foo = append(foo, "b")
foo = append(foo, "c")
for i, v := range {
fmt.Println(i, v)
}
// Output
0 a
1 b
2 c

It also worth noting that an initialized Slice has an underlying hidden Array which means it shares storage with the Array. Besides length, Slice also have capacity which is the length of the underlying Array. Appending new value beyond the capacity will allocates new Array that fits the Slice elements.

Zero Value

In Golang, when a variable is declared without an initialization value, its value will be set to the type’s zero value. The zero value of a Slice is nil, so in our example above, when we declare var foo []string the value of foo is actually nil not an empty slice of string []. Empty slice can be generated by using Short Variable Declarations eg. foo := []string{} or make function.

Why is This Important?

var nilSlice []string
emptySlice := make([]string, 5)
fmt.Println(nilSlice) // Output: []
fmt.Println(len(nilSlice), cap(nilSlice)) // Output: 0 0
fmt.Println(nilSlice == nil) // Output: true
fmt.Println(emptySlice) // Output: []
fmt.Println(len(emptySlice), cap(emptySlice)) // Output: 0 0
fmt.Println(emptySlice == nil) // Output: false

The above example demonstrate how Nil Slice can fool our eye. The result of fmt.Println -ing a Nil Slice is [] just like what we’ll have for an Empty Slice. It also has the same length and capacity as Empty Slice.

Most of the time, we don’t need to treat these two differently. We can still append and iterate a Nil Slice even though its initial value is nil. However, there are other cases that might be problematic.

type Res struct {
Data []string
}
var nilSlice []string
emptySlice := make([]string, 5)
res, _ := json.Marshal(Res{Data: nilSlice})
res2, _ := json.Marshal(Res{Data: emptySlice})
fmt.Println(string(res)) // Output: {"Data":null}
fmt.Println(string(res2)) // Output: {"Data":[]}

Golang’s encoding/json encodes Nil Slice to null which can be unacceptable if our API contract defines Data as a not null, array of string.

var nilSlice []string
emptySlice := make([]string, 5)
fmt.Println(reflect.DeepEqual(nilSlice, emptySlice)) // Output: false
fmt.Printf("Got: %+v, Want: %+v\n", nilSlice, emptySlice) //Output: Got: [], Want: []

Another problem is when we compare these two using reflect.DeepEqual which commonly used under the hood in assertion library. The comparison will be false which makes the assertion to fail, but the assertion message gives us the same [] which gives us no sleep.

--

--