深入理解golang中的nil

nil is (a) zero

什么是零值(zero value)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// go中的零值

bool            -> false
numbers         -> 0
string          -> ""

pointers    -> nil          // point to nothing
slices          -> nil          // have no backing array
maps                -> nil          // are not initialized
channels        -> nil          // are not initialized
functions       -> nil          // are not initialized
interfaces  -> nil          // have no value assigned, not even a  nil pointer

struct中的零值(zero values)

1
2
3
4
5
6
7
type Person struct{
  Age       int
  Name      string
  Friend    []Person    
}

var p Person // Person{0,"",nil}

nil 是什么类型(type)

“nil is a predeclared identifier in Go that represents zero values for pointers, interfaces, channels, maps, slices and function types.”

nil 是 Go 中预先声明的标识符,表示指针、接口、通道、映射、切片和函数类型的零值。

“nil"的多重特性

Nil Pointers

​ 指针是指向内存中某个位置的变量。因此,空指针将不指向任何位置。

Nil Slice

img

​ 它有三个属性:一个指向存储实际数据的底层数组的指针、一个表示长度的整型变量,以及另一个表示容量的整型变量。

​ 当你声明一个 nil 切片时,长度(len)和容量(cap)属性仍然存在;只有指向底层数组的指针为 nil。这意味着即使你的切片是 nil,你仍然可以在其上使用以下方法,并且完全有效。

1
2
3
var s []int
len(s)
cap(s)

Nil Interface

​ 在理解 nil 接口之前,你必须了解接口在内部是如何表示的。因此,它有两个属性:

1
(type, value)

​ type 表示接口实现的数据类型(如果提供),value 表示实际值。我们以 fmt.Stringer 接口为例,它只有一个方法 String()。

1
2
var s fmt.Stringer      // (nil, nil)
fmt.Println(s == nil)   // true

​ 这是因为 nil == nil 成立。

但如果我对这个接口进行一些修改,让它变成这样呢?你认为这个程序的输出会是什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Person struct{}

func (p Person) String() string {
	return ""
}

func main() {
	p := Person{}

	var s fmt.Stringer = p
	fmt.Println(s == nil)
}

​ 如果你猜的是 true,那么很抱歉,你错了。因为当我将一个"Person"结构的对象赋值给我们的接口"s"时,其内部布局变成了(Person, nil),正因如此,我们实际上是在比较 Person == nil,这并不成立。这就是为什么这个程序的输出会是"false”。

Nil Maps

​ 空Map 与空切片类似。你可以对其使用 len() 函数。你也可以遍历空映射,并且可以从空映射中获取值,但返回的将是默认值。唯一不能在空映射上进行的操作是赋值。以下是一段简单的代码来演示这一点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func NewGet(url string, headers map[string]string) (*http.Request, error) {

	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}

	for k, v := range headers {
		req.Header.Set(k, v)
	}

	return req, nil
}

​ 假设你有一个生成 HTTP 请求的函数。如果你需要向请求中添加头部信息,你会这样做。

1
2
3
NewGet("", map[string]string{
	"Content-Type": "application/json",
})

​ 但如果你不需要任何头部信息,你会这样做。

1
NewGet("", map[string]string{})

​ 有趣的是,即使你向这段代码传递 nil,它依然能够正常运行。

1
NewGet("", nil)

​ 因为我之前提到过,nil 映射并非空无一物。它意味着缺少底层的存储系统,但依然存在其他属性,使我们能够执行一些其他操作,比如迭代

一些关于nil的实践

1
2
3
4
5
6
7
//nil == nil

var s fmt.Stringer          // Stringer (nil,nil)
fmt.Println(s == nil)     // true
var p *Person                       // nil of type *Person
var s fmt.Stringer = p  // Stringer(*Person,nil)
fmt.Println(s == nil)       // false
nil not nil ?
  • Do not declare concrete error vars (不要声名错误变量)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// bad
type NameEmtpyError struct {
    name string
}

// NameEmtpyError实现了 Error() 方法的对象
func (e *NameEmtpyError) Error() string {
    return "name 不能为空"
}

func do() error {
    var err *NameEmtpyError
    return err                              // nil of type *NameEmtpyError
}

func main(){
  err := do()                               // error(*NameEmtpyError,nil)
    fmt.Println(err == nil)   // false
}
  • 不要返回具体的错误类型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// bad 
func do() *NameEmtpyError {
    return nil                              // nil of type *NameEmtpyError
}

func wrapDo() error {               // error(*NameEmtpyError, nil)
    return do()                             // nil of type *NameEmtpyError
}
func main(){
  err := wrapDo()                       // error(*NameEmtpyError,nil)
    fmt.Println(err == nil)     // false
}

nil is useful

1
2
3
4
5
6
pointers             // methods can be callend on nil receivers 方法可以在 nil 接收器上调用
slices               // perfectly valid zero values                         完全有效的零值映射
maps                     // perfect as read-only values                         完美的只读值
channels             // essential for some concurrency patterns 对于某些并发模式必不可少
functions        // needed for completeness                                 完整性所需
interfaces       // the most userd signal in Go(err != nil) Go中使用次数最多的信号(err != nil)