之前遇到一个关于给接口变量赋值为nil的问题,正好借此机会再学习一下相关的知识点
1 问题
先来看下面这段代码
func getErr() error {
return nil
}
type MyErr struct {
}
// MyErr 实现了error接口
func (m MyErr) Error() string {
return "MyErr"
}
func (m MyErr) getErr() *MyErr {
return nil
}
func main() {
e := getErr()
e = new(MyErr).getErr()
if e != nil {
fmt.Println("e is not nil")
}
}
# 输出结果
e is not nil
两处对变量e的赋值,看起来都是nil,但是在对e做判空的时候,却显示e并不是nil,不禁让人感到奇怪
2 相关知识点回顾
当给一个接口变量赋值的时候,该变量的动态类型会与它的动态值一起被存储在一个名为iface的专用数据结构中
这个iface的一个实例,才是赋给该接口变量的值
// src/runtime/runtime2.go
type iface struct {
tab *itab //指向类型信息的指针
data unsafe.Pointer // 指向动态值的指针
}
只有在以下场景中,接口变量才会是真正的nil:
-
声明一个接口变量但不做初始化
-
直接用字面量nil直接赋值
3 问题解析
我们结合知识点再回头来看上面的这段程序
这里声明了一个error接口的变量,并赋值为nil
e := getErr()
MyErr是接口error的一个动态类型
同时,在下面这行代码中,返回的nil就是变量e的动态值
e = new(MyErr).getErr()
所以,当上面这行代码执行完成后,e的实际值已经变成了iface类型的一个实例,而不再是nil了
因此,在我们做下面的判断时,就会进入if分支,并打印e is not nil的结果
if e != nil {
fmt.Println("e is not nil")
}