底层实现
// $GOROOT/src/runtime/runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
在 runtime 内部,interface 实际上有两种表示:
eface
用于表示没有方法的空接口( empty interface )类型变量,即interface{}
类型的变量。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 接口类型变量的内部表示。