[Go疑难杂症]为什么nil不等于nil

现象在日常开发中,可能一不小心就会掉进 Go 语言的某些陷阱里,而本文要介绍的 nil ≠ nil 问题,便是其中一个,初看起来会让人觉得很诡异,摸不着头脑 。
先来看个例子:
type CustomizedError struct { ErrorCode int Msgstring}func (e *CustomizedError) Error() string { return fmt.Sprintf("err code: %d, msg: %s", e.ErrorCode, e.Msg)}func main() { txn, err := startTx() if err != nil {log.Fatalf("err starting tx: %v", err) } if err = txn.doUpdate(); err != nil {log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}type tx struct{}func startTx() (*tx, error) { return &tx{}, nil}func (*tx) doUpdate() *CustomizedError { return nil}func (*tx) commit() error { return nil}这是一个简化过了的例子,在上述代码中,我们创建了一个事务,然后做了一些更新,在更新过程中如果发生了错误,希望返回对应的错误码和提示信息 。
如果感兴趣的话,可以在这个地址在线运行这份代码:
Go Playground - The Go Programming Language
看起来每个方法都会返回 nil,应该能顺利走到最后一行,输出 success 才对 , 但实际上,输出的却是:
err updating: <nil>寻找原因为什么明明返回的是 nil,却被判定为 err ≠ nil 呢?难道这个 nil 也有什么奇妙之处?
这就需要我们来更深入一点了解 error 本身了 。在 Go 语言中,error 是一个 interface  , 内部含有一个 Error() 函数 , 返回一个字符串,接口的描述如下:
// The error built-in interface type is the conventional interface for// representing an error condition, with the nil value representing no error.type error interface { Error() string}而对于一个变量来说 , 它有两个要素 , 一个是 type T,一个是 value V , 如下图所示:

[Go疑难杂症]为什么nil不等于nil

文章插图
来看一个简单的例子:
var it interface{}fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // <nil> <invalid reflect.Value>it = 1fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // int 1it = "hello"fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // string hellovar s *stringit = sfmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string <nil>ss := "hello"it = &ssfmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string 0xc000096560在给一个 interface 变量赋值前,TV 都是 nil , 但给它赋值后 , 不仅会改变它的值,还会改变它的类型 。
当把一个值为 nil 的字符串指针赋值给它后 , 虽然它的值是 V=nil , 但它的类型 T 却变成了 *string
此时如果拿它来跟 nil 比较,结果就会是不相等,因为只有当这个 interface 变量的类型和值都未被设置时,它才真正等于 nil
再来看看之前的例子中,err 变量的 TV 是如何变化的:
func main() { txn, err := startTx() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) if err != nil {log.Fatalf("err starting tx: %v", err) } if err = txn.doUpdate(); err != nil {fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}输出如下:
<nil> <invalid reflect.Value>*err.CustomizedError <nil>在一开始,我们给 err 初始化赋值时,startTx 函数返回的是一个 error 接口类型的 nil 。此时查看其类型 T 和值 V 时,都会是 nil
txn, err := startTx()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // <nil> <invalid reflect.Value>func startTx() (*tx, error) { return &tx{}, nil}而在调用 doUpdate 时,会将一个 *CustomizedError 类型的 nil 值赋值给了它,它的类型 T 便成了 *CustomizedError ,V 是 nil
err = txn.doUpdate()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // *err.CustomizedError <nil>所以在做 err ≠ nil 的比较时,err 的类型 T 已经不是 nil,前面已经说过,只有当一个接口变量的

推荐阅读