nils in Go
nil is a frequently used and important predeclared identifier in Go. It is the literal representation of zero values of many kinds of types. Many new Go programmers with experiences of some other popular languages may view nil as the counterpart of null (or NULL) in other languages. This is partly right, but there are many differences between nil in Go and null (or NULL) in other languages.
nil.
nil Is a Predeclared Identifier in Go
nil without declaring it.
nil Can Represent Zero Values of Many Types
nil can represent zero values of the following kinds of types:
nil Has Not a Default Type
true and false are both bool type.
iota is int.
nil has not a default type, though it has many possible types. In fact, the predeclared nil is the only untyped value who has not a default type in Go. There must be sufficient information for compiler to deduce the type of a nil from context.
package main
func main() {
// There must be sufficient information for
// compiler to deduce the type of a nil value.
_ = (*struct{})(nil)
_ = []int(nil)
_ = map[int]bool(nil)
_ = chan string(nil)
_ = (func())(nil)
_ = interface{}(nil)
// These lines are equivalent to the above lines.
var _ *struct{} = nil
var _ []int = nil
var _ map[int]bool = nil
var _ chan string = nil
var _ func() = nil
var _ interface{} = nil
// This following line doesn't compile.
var _ = nil
}
nil Is Not a Keyword in Go
nil can be shadowed.
package main
import "fmt"
func main() {
nil := 123
fmt.Println(nil) // 123
// The following line fails to compile,
// for nil represents an int value now
// in this scope.
var _ map[string]int = nil
}
null and NULL in many other languages are also not keywords.)
nil). The size of a nil value is always the same as the sizes of non-nil values whose types are the same as the nil value. But nil values of different kinds of types may have different sizes.
package main
import (
"fmt"
"unsafe"
)
func main() {
var p *struct{} = nil
fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []int = nil
fmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]bool = nil
fmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan string = nil
fmt.Println( unsafe.Sizeof( c ) ) // 8
var f func() = nil
fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{} = nil
fmt.Println( unsafe.Sizeof( i ) ) // 16
}
nil are always the same. For example, the sizes of all values of all different slice types are the same.
// Compilation failure reason: mismatched types.
var _ = (*int)(nil) == (*bool)(nil) // error
var _ = (chan int)(nil) == (chan bool)(nil) // error
nil values are not exceptions of the comparison rules.
type IntPtr *int
// The underlying of type IntPtr is *int.
var _ = IntPtr(nil) == (*int)(nil)
// Every type in Go implements interface{} type.
var _ = (interface{})(nil) == (*int)(nil)
// Values of a directional channel type can be
// converted to the bidirectional channel type
// which has the same element type.
var _ = (chan int)(nil) == (chan<- int)(nil)
var _ = (chan int)(nil) == (<-chan int)(nil)
var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)
nil identifier.
// The following lines compile okay.
var _ = ([]int)(nil) == nil
var _ = (map[string]int)(nil) == nil
var _ = (func())(nil) == nil
false. The reason is the non-interface value will be converted to the type of the interface value before making the comparison. The converted interface value has a dynamic type but the other interface value has not. That is why the comparison result is always false.
fmt.Println( (interface{})(nil) == (*int)(nil) ) // false
fmt.Println( (map[string]int)(nil)["key"] ) // 0
fmt.Println( (map[int]bool)(nil)[123] ) // false
fmt.Println( (map[int]*int64)(nil)[123] ) // <nil>
0, 1, 2, 3 and 4, then block for ever. Hello, world and Bye will never be printed.
for range []int(nil) {
fmt.Println("Hello")
}
for range map[string]string(nil) {
fmt.Println("world")
}
for i := range (*[5]int)(nil) {
fmt.Println(i)
}
for range chan bool(nil) { // block here
fmt.Println("Bye")
}
package main
type Slice []bool
func (s Slice) Length() int {
return len(s)
}
func (s Slice) Modify(i int, x bool) {
s[i] = x // panic if s is nil
}
func (p *Slice) DoNothing() {
}
func (p *Slice) Append(x bool) {
*p = append(*p, x) // panic if p is nil
}
func main() {
// The following selectors will not cause panics.
_ = ((Slice)(nil)).Length
_ = ((Slice)(nil)).Modify
_ = ((*Slice)(nil)).DoNothing
_ = ((*Slice)(nil)).Append
// The following two lines will also not panic.
_ = ((Slice)(nil)).Length()
((*Slice)(nil)).DoNothing()
// The following two lines will panic. But panics
// will not be triggered at the time of invoking
// the methods. They will be triggered on
// dereferencing nil pointers in the method bodies.
/*
((Slice)(nil)).Modify(0, true)
((*Slice)(nil)).Append(true)
*/
}
Append method is not perfect, it should be modified as the following one.
func (p *Slice) Append(x bool) {
if p == nil {
*p = []bool{x}
return
}
*p = append(*p, x)
}
*new(T) Results a Nil T Value if the Zero Value of Type T Is Represented With the Predeclared nil Identifier
package main
import "fmt"
func main() {
fmt.Println(*new(*int) == nil) // true
fmt.Println(*new([]int) == nil) // true
fmt.Println(*new(map[int]bool) == nil) // true
fmt.Println(*new(chan string) == nil) // true
fmt.Println(*new(func()) == nil) // true
fmt.Println(*new(interface{}) == nil) // true
}
nil is designed as an identifier which can be used to represent the zero values of some kinds of types. It is not a single value. It can represent many values with different memory layouts.
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect standard package.sync standard package.sync/atomic standard package.