输出如下:
<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
, 前面已经说过 , 只有当一个接口变量的 T
和 V
同时为 nil
时 , 这个变量才会被判定为 nil
, 所以该不等式会判定为 true
。
要修复这个问题 , 其实最简单的方法便是在调用 doUpdate
方法时给 err
进行重新声明:
if err := txn.doUpdate(); err != nil {log.Fatalf("err updating: %v", err)}
此时 , err
其实成了一个新的结构体指针变量 , 而不再是一个interface
类型变量 , 类型为 *CustomizedError
, 且值为 nil
, 所以做 err ≠ nil
的比较时结果就是将是 false
。
问题到这里似乎就告一段落了 , 但 , 再仔细想想 , 就会发现这其中似乎还是漏掉了一环 。
如果给一个 interface
类型的变量赋值时 , 会同时改变它的类型 T
和值 V
, 那跟 nil
比较时为什么不是跟它的新类型对应的 nil
比较呢?
事实上 , interface
变量跟普通变量确实有一定区别 , 一个非空接口 interface
(即接口中存在函数方法)初始化的底层数据结构是 iface
, 一个空接口变量对应的底层结构体为 eface
。
type iface struct { tab*itab data unsafe.Pointer}type eface struct { _type *_type dataunsafe.Pointer}
tab
中存放的是类型、方法等信息 。data
指针指向的 iface
绑定对象的原始数据的副本 。
【[Go疑难杂症]为什么nil不等于nil】再来看一下 itab
的结构:
// layout of Itab known to compilers// allocated in non-garbage-collected memory// Needs to be in sync with// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.type itab struct { inter *interfacetype _type *_type hashuint32 // copy of _type.hash. Used for type switches. _[4]byte // 用于内存对齐 fun[1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.}
itab
中一共包含 5 个字段 , inner
字段存的是初始化 interface
时的静态类型 。_type
存的是 interface
对应具体对象的类型 , 当 interface
变量被赋值后 , 这个字段便会变成被赋值的对象的类型 。
itab
中的 _type
和 iface
中的 data
便分别对应 interface
经验总结扩展阅读
- 十二星座的爱情为什么总在十万八千里
- 菊花茶泡了为什么会变绿
- 新鲜花椒冷冻为什么要放水
- 贵州为什么叫黔
- 电脑?号怎么打出来(电脑为什么打不出来字)
- 岩茶第一泡为什么叫还魂汤
- 网上买的鲜花要醒花多久 网购鲜花为什么要醒花
- 网上买的鲜花回来怎么泡好 网购鲜花为什么要醒花
- 西瓜为什么叫西瓜呢
- 床上为什么会有小虫子