引言
- 从runtime包分析学习interface底层结构和interface断言的实现、
是nil,但不完全是nil
func InterfaceTest(v interface{}) {
fmt.Println(v)
if v == nil {
fmt.Println("v is nil")
return
}
fmt.Println("v is not nil")
}
func InterfaceTest2(v *int) {
fmt.Println(v)
if v == nil {
fmt.Println("v is nil")
return
}
fmt.Println("v is not nil")
}
func main() {
var v *int = nil
InterfaceTest(v)
fmt.Println("")
InterfaceTest2(v)
}
运行main方法,查看输出结果发现:InterfaceTest2是符合预期的。从InterfaceTest的输出来看,v是nil,但不完全为nil,经过interface转换之后发生了什么?
<nil>
v is not nil
<nil>
v is nil
interface底层结构
interface判断与预期不一样的根本原因,interface根本不是那么简简单单,而是两个struct。什么?interface不是单纯的值?是struct,还是两个?
根据interface是否包含method,底层实现上有两种struct:eface,iface
eface表示不包含任何方法的空接口,也称为 empty interface。
type eface struct { _type *_type // 指向类型信息 data unsafe.Pointer // 指向值信息 } type _type struct { size uintptr // 类型的大小 ptrdata uintptr // 所有指针的内存前缀大小 hash uint32 // 类型的hash值。此处提前计算好,可以避免在哈希表中计算 tflag tflag // 额外的类型信息标志。此处为类型的 flag 标志,主要用于反射 align uint8 // 对应变量与该类型的内存对齐大小。 fieldalign uint8 // 对应类型的结构体的内存对齐大小。 kind uint8 // 类型的枚举值。包含 Go 语言中的所有类型,例如:kindBool、kindInt、kindInt8、kindInt16 等。 alg *typeAlg // algorithm table gcdata *byte // 存储垃圾收集器的 GC 类型数据 str nameOff // string form ptrToThis typeOff // type for pointer to this type, may be zero }
iface包含一些method的具体实现,存在itab.fun变量里
type iface struct { tab *itab data unsafe.Pointer } type itab struct { inter *interfacetype // 接口的类型信息 _type *_type // 具体类型信息,这个type和eface里的type是一个东西 link *itab bad int32 inhash int32 // has this itab been added to hash? fun [1]uintptr // 底层数组,存储接口的方法集的具体实现的地址,其包含一组函数指针,实现了接口方法的动态分派,且每次在接口发生变更时都会更新 } type interfacetype struct { typ _type pkgpath name mhdr []imethod } type imethod struct { name nameOff ityp typeOff }
类型断言是怎么实现的?
- 断言分为安全和非安全两种
iface和eface对应不同的方法,安全和不安全也对应两种方法。(runtime包iface.go)func myAssert(v interface{}) { // 进行变量断言,若不判断容易出现 panic s := v.(string) // 进行安全断言 s, ok := v.(string) } func myAssert(v interface{}) { // 进行 switch 断言 switch v.(type) { case string: // do something case int: // do something case float64: // do something } }
func assertI2I(inter *interfacetype, i iface) (r iface) { tab := i.tab if tab == nil { // explicit conversions require non-nil interface value. panic(&TypeAssertionError{nil, nil, &inter.typ, ""}) } if tab.inter == inter { r.tab = tab r.data = i.data return } r.tab = getitab(inter, tab._type, false) r.data = i.data return } func assertI2I(inter *interfacetype, i iface) (r iface) func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) func assertE2I(inter *interfacetype, e eface) (r iface)