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

变量的 TV , _type 是这个变量对应的类型 , data 是这个变量的值 。在之前的赋值测试中 , 通过 reflect.TypeOfreflect.ValueOf 方法获取到的信息也分别来自这两个字段 。
这里的 hash 字段和 _type 中存的 hash 字段是完全一致的 , 这么做的目的是为了类型断言 。
fun 是一个函数指针 , 它指向的是具体类型的函数方法 , 在这个指针对应内存地址的后面依次存储了多个方法 , 利用指针偏移便可以找到它们 。
再来看看 interfacetype 的结构:
type interfacetype struct { typ_type pkgpath name mhdr[]imethod}这其中也有一个 _type 字段 , 来表示 interface 变量的初始类型 。
看到这里 , 之前的疑问便开始清晰起来 , 一个 interface 变量实际上有两个类型 , 一个是初始化时赋值时对应的 interface 类型 , 一个是赋值具体对象时 , 对象的实际类型 。
了解了这些之后 , 我们再来看一下之前的例子:
txn, err := startTx()这里先对 err 进行初始化赋值 , 此时 , 它的 itab.inter.typ 对应的类型信息就是 erroritab._type 仍为 nil
err = txn.doUpdate()当对 err 进行重新赋值时 , erritab._type 字段会被赋值成 *CustomizedError  , 所以此时 , err 变量实际上是一个 itab.inter.typerror  , 但实际类型为 *CustomizedError  , 值为 nil 的接口变量 。
把一个具体类型变量与 nil 比较时 , 只需要判断其 value 是否为 nil 即可 , 而把一个接口类型的变量与 nil 进行比较时 , 还需要判断其类型 itab._type 是否为nil
如果想实际看看被赋值后 err 对应的 iface 结构 , 可以把 iface 相关的结构体都复制到同一个包下 , 然后通过 unsafe.Pointer 进行类型强转 , 就可以通过打断点的方式来查看了 。
func TestErr(t *testing.T) { txn, err := startTx() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) if err != nil {log.Fatalf("err starting tx: %v", err) } p := (*iface)(unsafe.Pointer(&err)) fmt.Println(p.data) if err = txn.doUpdate(); err != nil {fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))p := (*iface)(unsafe.Pointer(&err))fmt.Println(p.data)log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}

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

文章插图
补充说明一下 , 这里的inter.typ.kind 表示的是变量的基本类型 , 其值对应 runtime 包下的枚举 。
const ( kindBool = 1 + iota kindInt kindInt8 kindInt16 kindInt32 kindInt64 kindUint kindUint8 kindUint16 kindUint32 kindUint64 kindUintptr kindFloat32 kindFloat64 kindComplex64 kindComplex128 kindArray kindChan kindFunc kindInterface kindMap kindPtr kindSlice kindString kindStruct kindUnsafePointer kindDirectIface = 1 << 5 kindGCProg= 1 << 6 kindMask= (1 << 5) - 1)比如上图中所示的 kind = 20 对应的类型就是

经验总结扩展阅读