底层实现

// $GOROOT/src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}
 
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

在 runtime 内部,interface 实际上有两种表示:

  1. eface 用于表示没有方法的空接口( empty interface )类型变量,即 interface{} 类型的变量。
  2. iface 用于表示其余拥有方法的接口类型变量。

eface 表示的空接口类型没有方法列表,所以第一个字段是一个指向当前存储变量的动态类型信息(_type),而 iface 除了存储变量的动态类型信息外,还要包含接口本身的信息,所以第一个字段指向的是一个同时包含这两个信息的结构体 itab 的指针。itab 结构体中也有指向当前存储变量的动态类型结构体(_type)的指针。

Golang 程序运行时,运行时会为程序中所有的类型都建立只读共享的 _type 信息表,所有拥有相同类型的值会指向同一个 _type 结构体。

判断接口是否相等

虽然空接口与非空接口在内部表示上存在差异,可在判断两个 interface 时可以忽略这中结构上的差异,而把所有 interface 的变量内部看作由两部分构成:(T, V) 类型和值。判断是否相等时,只有 (T, V) 都相等时才相等。比如下面这个例子:

type MyErr struct {
    error
}
 
func returnErr() error {
    var e *MyErr = (*MyErr)(nil)
    return e
}
 
func main() {
    e := returnErr()
    fmt.Println(e)
    fmt.Println(e == nil)
}

运行结果为:

<nil>
false

原因在于,当我们申明 var e *MyErr,我们实际上同时将 *MyErr 的类型信息放入 e(T=*MyErr, V=nil)。所以在 main 中和 nil 比较时相当于 (T=*MyErr, V=nil) == (T=nil, V=nil) 结果就是不相等。

要解决这个问题,我们可以在 returnErr 中选择直接返回 return nil 就行。另外还要注意,这里 interface 和 struct 的不同,interface 在只是声明没有存值的情况下其内部值相当于 (T=nil, V=nil),而 struct 在声明之后就带上了类型属性,再转换为 interface 时类型就不再为 nil

所以下面这样的 returnErr 的返回值可以正常判 nil

func returnErr() (e error) {
  return
}
 
func returnErr() error {
  var e error
  return e
}

Reference

  • Golang FAQ
  • 《Go 语言精进之路:从新手到高手的编程思想、方法和技巧1》,白明,26.2 接口类型变量的内部表示。