nils In Go

nil is a familiar 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.

The remaining of this article will list all kinds of facts and details related to nil.

nil Is A Predeclared Identifier In Go

You can use nil without declaring it.

nil Can Represent Zero Values Of Many Types

In Go, nil can represent zero values of the following kinds of types:

In other words, in Go, nil may be many values, of different types.

nil Has Not A Default Type

Each of other predeclared identifiers in Go has a default type. For example,

But nil has not a default type, though it has many possible types. There must be sufficient information for compiler to deduce the type of a nil value from context.

Example:
package main

func main() {
	// This following line doesn't compile.
	/*
	v := nil
	*/

	// 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)

	// This 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
}

nil Is Not A Keyword In Go

The predeclared nil can be shadowed.

Example:
package main

import "fmt"

func main() {
	nil := 123
	fmt.Println(nil) // 123
	
	// The following line will compile error, for
	// nil represents an int value now in this scope.
	/*
	var _ map[string]int = nil
	*/
}

(BTW, null and NULL in many other languages are also not keywords.)

The Sizes Of nil Values With Types Of Different Kinds May Be Different

The memory layouts of all value of a type are always the same. nil values of the type are not exceptions. 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. So nil identifiers representing different zero values of different types may have different sizes.

Example:
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
}

The sizes are compiler and architecture dependent. The above printed results are for 64-bit architectures and the standard Go compiler. For 32--bit architectures, the printed sizes will be half.

For the standard Go compiler, the sizes of two nil values of different types of the same kind are always the same. For example, two nil values of two different slice types, []int and []string, are the same.

Two nil Values Of Two Different Types May Be Not Comparable

For example:
// Following lines don't compile.
var _ = (*int)(nil) == (*bool)(nil)         // error: mismatched types.
var _ = (chan int)(nil) == (chan bool)(nil) // error: mismatched types.

Please read comparation rules in Go to get which values can be compared with each other. Typed nil values are not exceptions of the comparation rules.

The code lines in the following example all compile okay.
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)

Two nil Values Of The Same Type May Be Not Comparable

In Go, map. slice and function types don't support comparison. So comparing two nil identifiers specified with any type of the uncomparable types is illegal.
// The following lines fail to compile.
var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)
But any values of the above mentioned uncomparable types can be compared with a bare nil identifier.
// The following lines compile okay.
var _ = ([]int)(nil) == nil
var _ = (map[string]int)(nil) == nil
var _ = (func())(nil) == nil

Two nil Values May Be Not Equal

If one of the two compared nil values is an interface value and the other is not, assume they are comparable, then the comparison result is always 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 concrete dynamic type but the other interface value has not. That is why the comparison result is always false.

Example:
fmt.Println( (interface{})(nil) == (*int)(nil) ) // false

Retrieving Elements From Nil Maps Will Not Panic

Retrieving element from a nil map value will always return a zero element value.

For example:
fmt.Println( (map[string]int)(nil)["key"] ) // 0
fmt.Println( (map[int]bool)(nil)[123] )     // false
fmt.Println( (map[int]*int64)(nil)[123] )   // <nil>

It Is Legal To Range Over Nil Channels, Maps, Slices, And Array Pointers

The number of loops steps by iterate nil maps and slices is zero.

The number of loops steps by iterate a nil array pointer is the length of its corresponding array type. (However, if the length of the corresponding array type is not zero, and the second iteration is neither ignored nor omitted, the iteration will panic at run time.)

Ranging over a nil channel will block for ever.

For example, the following code will print 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")
}

Invoking Methods Through Non-Interface nil Arguments Will Not Panic

Example:
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.
	// It will be triggered in the method bodies.
	/*
	((Slice)(nil)).Modify(0, true)
	((*Slice)(nil)).Append(true)
	*/
}

*new(T) Is Equal To nil If The Zero Value Of Type T Is Represented With nil

Example:
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
}

Summary

In Go, nil is just an identifier which can be used to represent the zero values of some kinds of types. It is not a single value. On the contrary, it can represent many values with different memory layouts.

The Go 101 project is hosted on both Github and Gitlab. 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.

Support Go 101 by playing Tapir's games. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212