When starting to code in Go, I encountered the following situation. I needed to create an empty slice, so I did:
slice := []string{}
However, my IDE flagged it as a warning, and pointed me to this Go style guide passage, which recommended using a nil slice instead:
var slice []string
This recommendation didn’t seem right to me. How can a nil variable be better? Won’t I run into issues like null pointer exceptions and other annoyances? Well, as it turns out, that’s not how slices work in Go. When declaring a nil slice, it is not the dreaded null pointer. It is still a slice. This slice includes a slice header, but its value just happens to be nil.
The main difference between a nil slice and an empty slice is the following. A nil slice compared to nil will return true. That’s pretty much it.
if slice == nil {
fmt.Println("Slice is nil.")
} else {
fmt.Println("Slice is NOT nil.")
}
When printing a nil slice, it will print like an empty slice:
fmt.Printf("Slice is: %v\n", slice)
Slice is: []
You can append to a nil slice:
slice = append(slice, "bozo")
You can loop over a nil slice, and the code will not enter the for loop:
for range slice {
fmt.Println("We are in a for loop.")
}
The length of a nil slice is 0:
fmt.Printf("len: %#v\n", len(slice))
len: 0
And, of course, you can pass a nil slice by pointer. That’s right – pass a nil slice by pointer.
func passByPointer(slice *[]string) {
fmt.Printf("passByPointer len: %#v\n", len(*slice))
*slice = append(*slice, "bozo")
}
You will get the updated slice if the underlying slice is reassigned.
passByPointer(&slice)
fmt.Printf("len after passByPointer: %#v\n", len(slice))
len after passByPointer: 1
The code above demonstrates that a nil slice is not a nil pointer. On the other hand, you cannot dereference a nil pointer like you can a nil slice. This code causes a crash:
var nullSlice *[]string
fmt.Printf("Crash: %#v\n", len(*nullSlice))
Here’s the full gist: