Go-iface和eface

219 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情

前言

总所周知,interface是Go中的一个关键字,是一组方法的集合,只定义了方法的形态,同时也是Go中多态的一种实现方式。

type interfaceName interface {
    
    TODO()  
​
    // ...some method
}

关于接口的命名,我们通常以er作为后缀,表示某个行为。根据接口中是否有定义方法,运行时Go中分为了两个结构体来代表,分别是ifaceeface

在了解这两个结构体时,我们先来看看一个常见的interface面试题,下面这个例子输出什么?

 type People interface {
      Show()
  }
​
  type Student struct{}
​
  func (stu *Student) Show() {
​
  }
​
  func live() People {
      var stu *Student
      return stu
  }
​
  func main() {
      if live() == nil {
          fmt.Println("AAAAAAA")
      } else {
          fmt.Println("BBBBBBB")
      }
  }

了解interface结构体就知道答案是输出:BBBBBBB。那么下面就来学习下Go对interface做了啥操作把,这两个结构体的源码在runtime/runtime2.go下就可以找到啦

iface

当接口里有定义方法时,就会使用iface这个结构体,它具有两个属性:

type iface struct {
    tab  *itab
    data unsafe.Pointer
}
  • tab用来存储接口本身的相关信息,例如:接口的类型信息、方法集信息、具体类型信息以及具体类型信息的方法集
  • data则指向当前具体类型的值
type itab struct {
    inter *interfacetype // 接口信息
    _type *_type // 具体类型的信息
    hash  uint32 // 类型转换的hash值,用于接口转换为具体类型时
    _     [4]byte // 对象对齐
    fun   [1]uintptr // 一组函数指针
}
​
type interfacetype struct {
    typ     _type // 接口类型
    pkgpath name // 接口包路径
    mhdr    []imethod // 接口方法集
}

下面来看个例子

type Sayer interface {
    Say(word string)
}
​
type People struct {
}
​
func (p *People) Say(word string) {
    fmt.Println("People:", word)
}
​
func main() {
    var people *People
    var sayer Sayer
    fmt.Println(people == nil) // true
    fmt.Println(sayer == nil) // true
    sayer = people
    fmt.Println(sayer == nil) // false
}
  • people是一个引用类型变量,因此它的零值是nil,所以people==nil为true
  • sayer也同样是一个引用类型变量,它的两个属性tabdata都为nil,因此sayer==nil也为true
  • 将people赋值给sayer,这时tab属性会存储相关的具体类型和接口类型,因此tab属性不为nil,data属性被赋值为具体属性people的值,同样是nil。这时在sayer==nil就为false了。

image.png 因此只有一个iface接口,tab属性和data属性都为nil时,该接口才为nil。

eface

因为eface只用来表示方法集为空的接口,因此eface就是iface的缩减版。

只使用_type属性指向具体类型信息,data同样指向具体类型变量的值。

type eface struct {
   _type *_type // 具体类型信息
   data  unsafe.Pointer // 具体类型变量值
}

image-20221021154437837

这时回头看那个面试题就知道为啥啦,live()方法返回stu变量,同时因为方法返回值为People类型,因此发生了一次隐形赋值,类似如下

var people People = stu
return people

这时iface结构体的tab属性就不为nil啦,表达式live()==nil为false。

参考资料