Golang中interface的nil判断

背景

最近在使用Golang的过程中遇到了一些有意思的问题,主要是关于interfacenil判断。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type IShape interface {
Draw()
}

type Circle struct {
Radius int
}

func (c *Circle) Draw() {
fmt.Println(fmt.Sprintf("This is a circle. radius:%v", c.Radius))
}

func test(shape IShape) {
if shape == nil {
fmt.Println("input a nil data")
} else {
shape.Draw()
}
}

func main() {
circle := &Circle{Radius: 1}
circle = nil
test(circle)
}

运行结果

以上代码是基本还原了我的遇到的情况,运行后会panic

1
2
3
4
5
6
7
8
9
10
11
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x109ea96]

goroutine 1 [running]:
main.(*Circle).Draw(0x0)
/Users/wangsai/Downloads/test.go:14 +0x26
main.test(0x10ec1c0, 0x0)
/Users/wangsai/Downloads/test.go:21 +0x3a
main.main()
/Users/wangsai/Downloads/test.go:28 +0x52
exit status 2

我们尝试将main函数修改成如下:

1
2
3
4
5
6
func main() {
var circle IShape
circle = &Circle{Radius: 1}
circle = nil
test(circle)
}

正常运行,输出如下:

1
input a nil data

原因分析

开始的时候,虽然我们显式的将指针赋值为nil,但是显然没有生效。我们对结果进行打印

1
fmt.Println(fmt.Sprintf("data:%v, data is nil:%v", shape, shape == nil))

输出结果如下:

1
data:<nil>, data is nil: false

还是看不出问题所在。

在网上查询相关资料,找到了官方的一些相关解释 nil_error:

Under the covers, interfaces are implemented as two elements, a type T and a value V. V is a concrete value such as an int, struct or pointer, never an interface itself, and has type T. For instance, if we store the int value 3 in an interface, the resulting interface value has, schematically, (T=int, V=3). The value V is also known as the interface’s dynamic value, since a given interface variable might hold different values V (and corresponding types T) during the execution of the program.
An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (T=*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil.

简单来说就是interface底层实现是由两个元素组成: Type 和 Value。 只有两个都没有被设置的时候,interface才是nil。

综上,之所以我们将circle显式的置为nil,但是仍然失败,就是因为circle的type已经被设置过了。我们添加打印来印证我们的猜测:

1
fmt.Println(fmt.Sprintf("data:%v, type:%T, data is nil: %v", shape, shape, shape == nil))

结果如下,印证了上边的说法:

1
data:<nil>, type:*main.Circle data is nil: false

然后我尝试将打印语句从test函数移入到main中,得到了截然不同的结果

1
data:<nil>, type:*main.Circle, data is nil: true

原因是什么呢?很简单,这里的circle是pointer,而不是interface。