After reading lesson 27, you’ll be able to
The word nil is a noun that means nothing or zero. In the Go programming language, nil is a zero value. Recall from unit 2 that an integer declared without a value will default to 0. An empty string is the zero value for strings, and so on. A pointer with nowhere to point has the value nil. And the nil identifier is the zero value for slices, maps, and interfaces too.
Many programming languages incorporate the concept of nil, though they may call it NULL, null, or None. In 2009, prior to the release of Go, language designer Tony Hoare gave a presentation titled “Null References: The Billion Dollar Mistake.” In his talk (see mng.bz/dNzX), Hoare claims responsibility for inventing the null reference in 1965 and suggests that pointers to nowhere weren’t one of his brightest ideas.
Tony Hoare went on to invent communicating sequential processes (CSP) in 1978. His ideas are the basis for concurrency in Go, the topic of unit 7.
Nil is somewhat friendlier in Go, and less prevalent than in past languages, but there are still caveats to be aware of. Nil has some unexpected uses too, which Francesc Campoy talked about in his presentation at GopherCon 2016 (see www.youtube.com/watch?v=ynoY2xz-F8s), providing inspiration for this lesson.
Consider representing a constellation, where each star contains a pointer to its nearest neighboring star. After the math is done, every star will point somewhere, and finding the nearest star becomes a quick pointer dereference away.
But until all the calculations are done, where should the pointers point? This is one situation where nil comes in handy. Nil can stand in for the nearest star until it’s known.
What is another situation where a pointer to nowhere could be useful?
If a pointer isn’t pointing anywhere, attempting to dereference the pointer won’t work, as listing 27.1 demonstrates. Dereference a nil pointer, and the program will crash. As a rule, people tend to dislike apps that crash.
I call it my billion-dollar mistake.
Tony Hoare
var nowhere *int fmt.Println(nowhere) 1 fmt.Println(*nowhere) 2
Avoiding panic is fairly straightforward. It’s a matter of guarding against a nil pointer dereference with an if statement, as shown in the following listing.
var nowhere *int if nowhere != nil { fmt.Println(*nowhere) }
To be fair, programs can crash for many reasons, not only because of nil pointer dereferences. For example, divide by zero also causes a panic, and the remedy is similar. Even so, considering all the software written in the past 50 years, the number of accidental nil pointer dereferences could be fairly costly for users and programmers alike. The existence of nil does burden the programmer with more decisions. Should the code check for nil, and if so, where should the check be? What should the code do if a value is nil? Does all this make nil a bad word?
There’s no need to cover your ears or avoid nil altogether. In truth, nil can be quite useful, as the remainder of this lesson demonstrates. Additionally, nil pointers in Go are less prevalent than null pointers are in some other languages, and there are ways to avoid their use when appropriate.
What’s the zero value for the type *string?
Methods frequently receive a pointer to a structure, which means the receiver could be nil, as shown in the following listing. Whether a pointer is dereferenced explicitly (*p) or implicitly by accessing a field of the struct (p.age), a nil value will panic.
type person struct { age int } func (p *person) birthday() { p.age++ 1 } func main() { var nobody *person fmt.Println(nobody) 2 nobody.birthday() }
A key observation is that the panic is caused when the p.age++ line executes. Remove that line, and the program will run.
Contrast this to the equivalent program in Java, where a null receiver will crash the program immediately when a method is called.
Go will happily call methods even when the receiver has a nil value. A nil receiver behaves no differently than a nil parameter. This means methods can guard against nil values, as shown in the following listing.
func (p *person) birthday() { if p == nil { return } p.age++ }
Rather than check for nil before every call to the birthday method, the preceding listing guards against nil receivers inside the method.
In Objective-C, invoking a method on nil doesn’t crash, but rather than call the method, it returns a zero value.
You decide how to handle nil in Go. Your methods can return zero values, or return an error, or let it crash.
What does accessing a field (p.age) do if p is nil?
It panics, crashing the program, unless the code checks for nil before the field access.
When a variable is declared as a function type, its value is nil by default. In the following listing, fn has the type of a function, but it isn’t assigned to any specific function.
var fn func(a, b int) int fmt.Println(fn == nil) 1
If the preceding listing were to call fn(1, 2), the program would panic with a nil pointer dereference, because there’s no function assigned to fn.
It’s possible to check whether a function value is nil and provide default behavior. In the next listing, sort.Slice is used to sort a slice of strings with a first-class less function. If nil is passed for the less argument, it defaults to a function that sorts alphabetically.
package main import ( "fmt" "sort" ) func sortStrings(s []string, less func(i, j int) bool) { if less == nil { less = func(i, j int) bool { return s[i] < s[j] } } sort.Slice(s, less) } func main() { food := []string{"onion", "carrot", "celery"} sortStrings(food, nil) fmt.Println(food) 1 }
Write a line of code to sort food from the shortest to longest string in listing 27.6.
A slice that’s declared without a composite literal or the make built-in will have a value of nil. Fortunately, the range keyword, len built-in, and append built-in all work with nil slices, as shown in the following listing.
var soup []string fmt.Println(soup == nil) 1 for _, ingredient := range soup { fmt.Println(ingredient) } fmt.Println(len(soup)) 2 soup = append(soup, "onion", "carrot", "celery") fmt.Println(soup) 3
An empty slice and a nil slice aren’t equivalent, but they can often be used interchangeably. The following listing passes nil to a function that accepts a slice, skipping the step of making an empty slice.
func main() { soup := mirepoix(nil) fmt.Println(soup) 1 } func mirepoix(ingredients []string) []string { return append(ingredients, "onion", "carrot", "celery") }
Whenever you write a function that accepts a slice, ensure that a nil slice has the same behavior as an empty slice.
Which actions are safe to perform on a nil slice?
The built-ins len, cap, and append are safe to use with a nil slice, as is the range key-word. As with an empty slice, directly accessing an element of a nil slice (soup[0]) will panic with index out of range.
As with slices, a map declared without a composite literal or the make built-in has a value of nil. Maps can be read even when nil, as shown in the following listing, though writing to a nil map will panic.
var soup map[string]int fmt.Println(soup == nil) 1 measurement, ok := soup["onion"] if ok { fmt.Println(measurement) } for ingredient, measurement := range soup { fmt.Println(ingredient, measurement) }
If a function only reads from a map, it’s fine to pass the function nil instead of making an empty map.
What action on a nil map will cause a panic?
Writing to a nil map (soup["onion"] = 1) will panic with: assignment to entry in nil map.
When a variable is declared to be an interface type without an assignment, the zero value is nil. The following listing demonstrates that the interface type and value are both nil, and the variable compares as equal to nil.
var v interface{} fmt.Printf("%T %v %v ", v, v, v == nil) 1
When a variable with an interface type is assigned a value, the interface internally points to the type and value of that variable. This leads to the rather surprising behavior of a nil value that doesn’t compare as equal to nil. Both the interface type and value need to be nil for the variable to equal nil, as shown in the following listing.
var p *int v = p fmt.Printf("%T %v %v ", v, v, v == nil) 1
The %#v format verb is shorthand to see both type and value, also revealing that the variable contains (*int)(nil) rather than just <nil>, as shown in listing 27.12.
fmt.Printf("%#v ", v) 1
To avoid surprises when comparing interfaces to nil, it’s best to use the nil identifier explicitly, rather than pointing to a variable that contains a nil.
What’s the value of s when declared as var s fmt.Stringer?
The value is nil because fmt.Stringer is an interface and the zero value for interfaces is nil.
It can be tempting to adopt nil whenever a value can be nothing. For example, a pointer to an integer (*int) can represent both zero and nil. Pointers are intended for pointing, so using a pointer just to provide a nil value isn’t necessarily the best option.
Instead of using a pointer, one alternative is to declare a small structure with a few methods. It requires a little more code, but it doesn’t require a pointer or nil, as shown in the following listing.
type number struct { value int valid bool } func newNumber(v int) number { return number{value: v, valid: true} } func (n number) String() string { if !n.valid { return "not set" } return fmt.Sprintf("%d", n.value) } func main() { n := newNumber(42) fmt.Println(n) 1 e := number{} fmt.Println(e) 2 }
What are some advantages to the approach taken in listing 27.13?
It completely avoids nil pointer dereferences by not having pointers or nil values. The valid Boolean has a clear intention, whereas the meaning of nil is less clear.
Let’s see if you got this...
A knight blocks Arthur’s path. Our hero is empty-handed, represented by a nil value for leftHand *item. Implement a character struct with methods such as pickup(i *item) and give(to *character). Then use what you’ve learned in this lesson to write a script that has Arthur pick up an item and give it to the knight, displaying an appropriate description for each action.