在go语言中nil是一个经常使用的,重要的预先定义好的标识符。它是许多中类型的零值表示。 许多新有其他编程语言开发经验的go语言开发者都会把nil看作是其他语言中的null(NULL)。这是并不完全正确,因为go中的nil和其他语言中的null有很多不同点。
本文剩下的部分将会列出相关事实和细节。

​nil​​ 是go语言中预先定义的标识符。

我们可以直接使用nil,而不用声明它。

​nil​​可以代表很多类型的零值

在go语言中,​​nil​​可以代表下面这些类型的零值:

  • 指针类型(包括unsafe中的)
  • map类型
  • slice类型
  • function类型
  • channel类型
  • interface类型

预先定义的​​nil​​没有默认类型

go语言中每一个其他的预先定义的标识符都有一个默认类型,例如:

  • true和false的默认类型是bool
  • iota的预先定义类型是int

但是预先定义的​​nil​​​没有默认类型,尽管它有许多可能的类型。事实上,预先定义的​​nil​​​是唯一的一个go语言中没有默认类型的非类型值。对于编译器来说,必须从上下文中获取充足的信息才能推断出​​nil​​的类型。

 

package main

func main() {

_ = (*struct{})(nil)
_ = []int(nil)
_ = map[int]bool(nil)
_ = chan string(nil)
_ = (func())(nil)
_ = interface{}(nil)

// 下面这些行跟上面的等价
var _ *struct{} = nil
var _ []int = nil
var _ map[int]bool = nil
var _ chan string = nil
var _ func() = nil
var _ interface{} = nil

// 下面这行不编译
var _ = nil
}

预先定义的​​nil​​不是go语言中的关键字

不同类型的​​nil​​值占用的内存大小可能是不一样的

一个类型的所有的值的内存布局都是一样的。​​nil​​​也不例外。​​nil​​​的大小一致与同类型中的非nil类型的值的大小一样大。但是不同类型的​​nil​​​值的大小可能不同.
例如:

 

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
}

具体的大小取决于编译器和架构。上面打印的结果是在64位架构和标准编译器下完成的。对应32位的架构的,打印的大小将减半。

两个不同类型的​​nil​​值可能无法进行比较

例如: 下面的两个比较都将无法编译

 

var _ =(*int)(nil) ==(*bool)(nil)
var _ = (chan int)(nil) == (chan bool)(nil)

下面的可以编译

 

type IntPtr *int
// The underlying of type IntPtr is *int.
var _ = IntPtr(nil) == (*int)(nil)


//go中的每个类型都实现了interface{}类型
var _ = (interface{})(nil) == (*int)(nil)



//一个有向通道可以被转换成双向通道,因为他们有相同的元素类型
var _ = (chan int)(nil) == (chan<- int)(nil)
var _ = (chan int)(nil) == (<-chan int)(nil)

同一类型的两个​​nil​​值也可能无法比较

在go语言中map,slice和function不能比较。比较两个无法比较类型的值(包含nil)是非法的。下面的语句无法编译

 

var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)

但是,可以将上述不可比较类型的任何值与裸nil标识符进行比较。

 

// The following lines compile okay.
var _ = ([]int)(nil) == nil
var _ = (map[string]int)(nil) == nil
var _ = (func())(nil) == nil

两个​​nil​​值可能不相等

如果两个参与比较的nil值中有一个是interface值并且另外一个不是,假定他们可以比较,他们比较的结构总是false。 原因是在编译前非interface值将会被转换成interface值的类型。转换后的interface值有一个派生的动态类型,另外一个没有。这就是等于比较总是false的原因

 

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

从nil Map中遍历元素将不会panic

 

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

对nil channel,map,slice和array 指针进行range操作也是合法的。

对nil map和slice的循环次数将是0

对nil数组的循环次数将取决于它的数组类型定义的长度
对nil channel的range操作将永远阻塞当前goroutine
例如,下面的代码将打印0,1,2,3和4,然后永远阻塞。​​​hello, world​​​和​​bye​​将永远不会被打印

 

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")
}

通过非nil interface receiver 参数调用方法将不会panic

例如:

 

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() {
//下面的不会panic
_ = ((Slice)(nil)).Length
_ = ((Slice)(nil)).Modify
_ = ((*Slice)(nil)).DoNothing
_ = ((*Slice)(nil)).Append

// 下面两行将不会panic
_ = ((Slice)(nil)).Length()
((*Slice)(nil)).DoNothing()


//下面的两行代码将会panic,但是panic不是在调用时,而是在方法内部panic的
((Slice)(nil)).Modify(0, true)
((*Slice)(nil)).Append(true)
*/
}

如果T的零值是用预先定义的nil来表示的话,​​*new(T)​​产生一个nil T类型的值

例如:

 

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
}

总结

go语言中,nil是唯一的一个可以用来表示部分类型的零值的标识符。它不是一个单个值,它可以代表许多有不同内存布局的值