Golang-Interface底层是怎么实现的?


引言

  • 从runtime包分析学习interface底层结构和interface断言的实现、

是nil,但不完全是nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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转换之后发生了什么?

1
2
3
4
5
<nil>
v is not nil

<nil>
v is nil

interface底层结构

interface判断与预期不一样的根本原因,interface根本不是那么简简单单,而是两个struct。什么?interface不是单纯的值?是struct,还是两个?

e18d20c94006dfe0-9eef65073f0f6be0-d9c854df534cf3bd0dca74a7e620249c.jpeg

根据interface是否包含method,底层实现上有两种struct:eface,iface

  • eface表示不包含任何方法的空接口,也称为 empty interface。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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变量里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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
    }

类型断言是怎么实现的?

  • 断言分为安全和非安全两种

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    }
    }

    iface和eface对应不同的方法,安全和不安全也对应两种方法。(runtime包iface.go)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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)

参考

http://legendtkl.com/2017/07/01/golang-interface-implement/

https://mp.weixin.qq.com/s/vNACbdSDxC9S0LOAr7ngLQ


  目录