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

输出如下:
<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 , 前面已经说过 , 只有当一个接口变量的 TV 同时为 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 中的 _typeiface 中的 data 便分别对应 interface

经验总结扩展阅读