Go 语言中的接口 nil 检查需谨慎

51 天前
 pike0002

在 Go 语言中,特别是在错误检查方面,经常会看到nil 检查,这是由于 Go 语言的特殊错误处理约定。在大多数情况下,nil 检查是直截了当的,但在接口情况下,需要特别小心。

以下是一个代码片段,猜猜它的输出会是什么:

package main

import (
	"bytes"
	"fmt"
	"io"
)

func check(w io.Writer) {
	if w != nil {
		fmt.Println("w is not nil")
	}

	fmt.Printf("w is %+v\n", w)
}

func main() {
	var b *bytes.Buffer

	check(b)

	fmt.Printf("b is %+v", b)
}

输出结果

w is not nil
w is 
b is 

为什么会这样?

check()方法中,你可能期望wnil,但实际上并非如此。当打印该对象时,它显示为空。这是如何发生的呢?


接口的内部机制

Go 语言中的接口具有特殊的实现方式,它包含两个组成部分:

接口的底层表示是 (T, V) 的形式:

只有当 TV 都未设置时,接口值才会被认为是nil(即 T=nil, V=nil)。

特别地:


上述代码解释

当创建变量 b 时,它的类型是 *bytes.Buffer,但它的值是 nil。因此:


更具体的例子

以下代码进一步说明接口值什么时候为nil

package main

import (
	"fmt"
)

type SomeError struct{}

func (se *SomeError) Error() string {
	return "error"
}

func check(e error) {
	if e == nil {
		fmt.Println("e is nil")
	}

	fmt.Printf("e is %+v\n", e)
}

func main() {
	var e error = nil
	check(e)

	var se *SomeError = nil
	check(se)
}

输出结果

e is nil
e is <nil>
e is error

结论

在使用接口作为函数参数并进行nil检查时,要特别小心。如果接口的类型部分存在,即使nil,接口本身也不是nil

3391 次点击
所在节点    Go 编程语言
31 条回复
Elaina
51 天前
老坑了,里面长这样
```go
type eface struct {
_type *_type // 类型信息
data unsafe.Pointer
}

type iface struct {
tab *itab // 里面存储了一个类型信息字段
data unsafe.Pointer
}
```

不管空接口还是带方法的接口,都存了俩玩意儿
mizuki9
51 天前
@PTLin 你说的元数据部分,是我认为的 golang 引用类型的 type
mizuki9
51 天前
@PTLin 你说的是不是(*bytes.Buffer)nil != (any)nil 。
那确实是我的问题,我不懂 golang ,最近是在自学
PTLin
51 天前
@mizuki9 你看你上一楼的回答,类型信息肯定是有,还包括其他信息。接口类型也分为 inferface{}和其他的。我想说的是文章里的这个行为必须要知道接口的底层表示,和把一个实现了接口的类型赋值给接口会发生什么,才能理解文章中的问题。
leonshaw
51 天前
0 值也是一等公民
xuanbg
51 天前
有些人特别讨厌 null ,为了米有 null ,于是搞出了一些更奇怪的东西
liuguang
50 天前
nil 还有类型,这也是 go 语言的一大败笔设计
CRVV
50 天前
我刚学 Go 的时候也有这个问题,但后来用熟了再也没遇到过。
后来我从来没写过把 nil 传给 interface 的代码,个人观点是 99% 的情况下不需要也不应该这么写。
zhwguest
49 天前
@CLMan 我来杠一下,error 就是个接口好不好.....
Kauruus
47 天前
不明白为什么一个现代的语言还满地的 nil 。

也不明白为什么需要知道它的实现(接口值是类型+值 blah blah )才能理解它的行为。

能不能根据 The Go Programming Language Specification 来解释一下这个行为?
Kauruus
47 天前
> The static type (or just type) of a variable is the type given in its declaration, the type provided in the new call or composite literal, or the type of an element of a structured variable.

静态类型是变量声明时的类型。

> Variables of interface type also have a distinct dynamic type, which is the (non-interface) type of the value assigned to the variable at run time (unless the value is the predeclared identifier nil, which has no type).

接口类型变量还有一个动态的类型,这个类型是运行期赋于的*值的类型*,除非那个值是**预先声明的标识符 nil**,它没有类型。

这里很明确地区分了 标识符 nil 和一个值的类型。


解释了把 (标识符 nil )和 (一个为 nil 的值)赋值给一个 interface 的区别,前者没有类型,后者值为 nil 但是有类型。


> Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

两个接口值只有在动态类型和值都相等时相等。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1089816

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX