现象在日常开发中 , 可能一不小心就会掉进 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](http://shimg.jingyanzongjie.com/230726/2240531223-0.png)
文章插图
来看一个简单的例子:
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
变量赋值前 , T
和 V
都是 nil
, 但给它赋值后 , 不仅会改变它的值 , 还会改变它的类型 。当把一个值为
nil
的字符串指针赋值给它后 , 虽然它的值是 V=nil
, 但它的类型 T
却变成了 *string
。此时如果拿它来跟
nil
比较 , 结果就会是不相等 , 因为只有当这个 interface
变量的类型和值都未被设置时 , 它才真正等于 nil
。再来看看之前的例子中 ,
err
变量的 T
和 V
是如何变化的: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!")}
经验总结扩展阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 十二星座的爱情为什么总在十万八千里
- 菊花茶泡了为什么会变绿
- 新鲜花椒冷冻为什么要放水
- 贵州为什么叫黔
- 电脑?号怎么打出来(电脑为什么打不出来字)
- 岩茶第一泡为什么叫还魂汤
- 网上买的鲜花要醒花多久 网购鲜花为什么要醒花
- 网上买的鲜花回来怎么泡好 网购鲜花为什么要醒花
- 西瓜为什么叫西瓜呢
- 床上为什么会有小虫子