interface

Go 语言中 interface 的实现源码主要包括两个核心数据结构:eface 和 iface。

eface

eface 代表空接口 interface{}。它的定义如下:

type eface struct {
    _type *_type
    data unsafe.Pointer
}
  • _type 指向一个 _type 结构体,描述了该接口值的动态类型信息。

  • data 是一个指针,指向该接口值的实际数据。

iface

iface 代表非空接口,比如 io.Reader。它的定义如下:

type iface struct {
    tab *itab
    data unsafe.Pointer
}
  • tab 指向一个 itab 结构体,描述了该接口的元数据信息。
  • data 同 eface,指向实际数据。

itab

itab 结构体定义如下:

type itab struct {
    inter  *interfacetype
    _type  *_type
    hash   uint32
    _      [4]byte
    fun    [1]uintptr
}
  • inter 指向该接口自身的元数据。
  • _type 指向实际数据的类型信息。
  • hash 是类型的哈希值,用于快速比较。
  • fun 是一个函数指针数组,存储了该类型实现的接口方法。

当将一个具体类型的值赋给接口值时,编译器会根据该类型实现的方法生成一个 itab,并将其与实际数据的 _type 信息一起存储在 iface 中。这样在运行时,通过 iface.tab 就可以找到该值实现的接口方法集。

通过这种方式,Go 语言实现了接口的动态分发。当调用接口值的方法时,运行时会根据 iface.tab 找到正确的方法实现并执行。这种延迟绑定让不同类型的值可以被视为同一接口类型,实现了面向接口编程。

空接口 interface{} 的使用

var data *byte{}
var in interface{}

fmt.Println(data, data == nil) // <nil> true
fmt.Println(in, in == nil)      // <nil> true  

in = data
fmt.Println(in, in == nil)      // <nil> false

解析: 空接口 interface{} 可以存储任何类型的值,包括 nil。当将 nil 赋值给空接口时,其底层数据仍为 nil,但其类型信息已经存在,因此 in == nil 为 false

接口值比较

// 1. type 和 data 都相等
type ProfileInt interface {}

func main()  {
    var p1, p2 ProfileInt = Profile{"iswbm"}, Profile{"iswbm"}
    var p3, p4 ProfileInt = &Profile{"iswbm"}, &Profile{"iswbm"}

    fmt.Printf("p1 --> type: %T, data: %v \n", p1, p1)
    fmt.Printf("p2 --> type: %T, data: %v \n", p2, p2)
    fmt.Println(p1 == p2)  // true

    fmt.Printf("p3 --> type: %T, data: %p \n", p3, p3)
    fmt.Printf("p4 --> type: %T, data: %p \n", p4, p4)
    fmt.Println(p3 == p4)  // false
}

// 2. 两个都是nil
type ProfileInt interface {}

func main()  {
    var p1, p2 ProfileInt
    fmt.Println(p1==p2) // true
}

// 3.interface 与 非interface 比较

func main()  {
    var a string = "iswbm"
    var b interface{} = "iswbm"
    fmt.Println(a==b) // true
}

func main()  {
    var a *string = nil
    var b interface{} = a

    fmt.Println(b==nil) // false
}

解析: 在 Go 中,接口值的比较并不是比较其动态值,而是比较其动态类型和动态值。上例中 x和 y 的动态类型相同,但动态值不同(不同的内存地址),因此比较结果为 false

接口新概念(Go 1.18+)

Go 1.18 引入了一些新的接口相关概念:

  • Type Set(类型集合): 接口代表的是类型的集合,而不再是方法集合。
  • Specific Type(特定类型): 接口可以包含具体的类型,如 interface{int, string}
  • Structural Type(结构类型): 接口可以包含近似类型,如 interface{~string}

编译期间检查接口实现

type CommonResponseIface interface {
	GetCode() int
	GetMessage() string
	MergeMessage()
}

// 编译期间检查接口实现
var _ CommonResponseIface = (*CommonResponse)(nil)

type CommonResponse struct {
	Code    int         `json:"code"`    // 状态码 枚举: 200正常 500错误
	Data    interface{} `json:"data"`    // 数据包
	Message string      `json:"message"` // 消息。接口返回格式不统一,这边做个兼容
	Msg     string      `json:"msg"`     // 消息。接口返回格式不统一,这边做个兼容
}

func (c *CommonResponse) GetCode() int {
	return c.Code
}

func (c *CommonResponse) GetMessage() string {
	if c.Message != "" {
		return c.Message
	}
	return c.Msg
}

func (c *CommonResponse) MergeMessage() {
	if c.Message == "" {
		c.Message = c.Msg
	}
}