接口的比较性,具体规则为:
- 动态类型值为
nil
的接口变量总是相等的。 - 如果只有 1 个接口为
nil
,那么比较结果总是false
。 - 如果两个接口都不为
nil
,且接口变量具有相同的动态类型和动态类型值,那么两个接口是相同的。 - 如果接口存储的动态类型值是不可比较的,那么在运行时会报错。
1. nil 接口变量
未赋初值的接口类型变量的值为 nil
,这类变量也就是 nil
接口变量,我们来看这类变量的内部表示输出的例子:
func printNilInterface() {
// nil接口变量
var i interface{} // 空接口类型
var err error // 非空接口类型
println(i)
println(err)
println("i = nil:", i == nil)
println("err = nil:", err == nil)
println("i = err:", i == err)
}
运行这个函数,输出结果是这样的:
(0x0,0x0)
(0x0,0x0)
i = nil: true
err = nil: true
i = err: true
我们看到,无论是空接口类型还是非空接口类型变量,一旦变量值为 nil
,那么它们内部表示均为(0x0,0x0),也就是类型信息、数据值信息均为空。因此上面的变量 i
和 err
等值判断为 true
。
2. 空接口类型变量
下面是空接口类型变量的内部表示输出的例子:
func printEmptyInterface() {
var eif1 interface{} // 空接口类型
var eif2 interface{} // 空接口类型
var n, m int = 17, 18
eif1 = n
eif2 = m
println("eif1:", eif1)
println("eif2:", eif2)
println("eif1 = eif2:", eif1 == eif2) // false
eif2 = 17
println("eif1:", eif1)
println("eif2:", eif2)
println("eif1 = eif2:", eif1 == eif2) // true
eif2 = int64(17)
println("eif1:", eif1)
println("eif2:", eif2)
println("eif1 = eif2:", eif1 == eif2) // false
}
这个例子的运行输出结果是这样的:
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac580,0xc00007ef40)
eif1 = eif2: false
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac580,0x10eb3d0)
eif1 = eif2: true
eif1: (0x10ac580,0xc00007ef48)
eif2: (0x10ac640,0x10eb3d8)
eif1 = eif2: false
我们按顺序分析一下这个输出结果。
首先,代码执行到第 11 行时,eif1
与 eif2
已经分别被赋值整型值 17 与 18,这样 eif1
和 eif2
的动态类型的类型信息是相同的(都是 0x10ac580),但 data
指针指向的内存块中存储的值不同,一个是 17,一个是 18,于是 eif1
不等于 eif2
。
接着,代码执行到第 16 行的时候,eif2
已经被重新赋值为 17,这样 eif1
和 eif2
不仅存储的动态类型的类型信息是相同的(都是 0x10ac580),data
指针指向的内存块中存储值也相同了,都是 17,于是 eif1
等于 eif2
。
然后,代码执行到第 21 行时,eif2
已经被重新赋值了 int64
类型的数值 17。这样,eif1
和 eif2
存储的动态类型的类型信息就变成不同的了,一个是 int
,一个是 int64
,即便 data
指针指向的内存块中存储值是相同的,最终 eif1
与 eif2
也是不相等的。
从输出结果中我们可以总结一下:对于空接口类型变量,只有 _type
和 data
所指数据内容一致的情况下,两个空接口类型变量之间才能划等号。另外,Go
在创建 eface
时一般会为 data
重新分配新内存空间,将动态类型变量的值复制到这块内存空间,并将 data
指针指向这块内存空间。因此我们多数情况下看到的 data
指针值都是不同的。
3. 非空接口类型变量
我们也直接来看一个非空接口类型变量的内部表示输出的例子:
type T int
func (t T) Error() string {
return "bad error"
}
func printNonEmptyInterface() {
var err1 error // 非空接口类型
var err2 error // 非空接口类型
err1 = (*T)(nil)
println("err1:", err1)
println("err1 = nil:", err1 == nil)
err1 = T(5)
err2 = T(6)
println("err1:", err1)
println("err2:", err2)
println("err1 = err2:", err1 == err2)
err2 = fmt.Errorf("%d\n", 5)
println("err1:", err1)
println("err2:", err2)
println("err1 = err2:", err1 == err2)
}
这个例子的运行输出结果如下:
err1: (0x10ed120,0x0)
err1 = nil: false
err1: (0x10ed1a0,0x10eb310)
err2: (0x10ed1a0,0x10eb318)
err1 = err2: false
err1: (0x10ed1a0,0x10eb310)
err2: (0x10ed0c0,0xc000010050)
err1 = err2: false
我们看到上面示例中每一轮通过 println
输出的 err1
和 err2
的 tab
和 data
值,要么 data
值不同,要么 tab
与 data
值都不同。
和空接口类型变量一样,只有 tab
和 data
指的数据内容一致的情况下,两个非空接口类型变量之间才能划等号。这里我们要注意 err1
下面的赋值情况:
err1 = (*T)(nil)
针对这种赋值,println
输出的 err1
是(0x10ed120, 0x0),也就是非空接口类型变量的类型信息并不为空,数据指针为空,因此它与 nil
(0x0,0x0)之间不能划等号。
type MyError struct {
error
}
var ErrBad = MyError{
error: errors.New("bad things happened"),
}
func bad() bool {
return false
}
func returnsError() error {
var p *MyError = nil
if bad() {
p = &ErrBad
}
return p
}
func main() {
err := returnsError()
if err != nil {
fmt.Printf("error occur: %+v\n", err)
return
}
fmt.Println("ok")
}
现在我们再回到我们开头的那个问题,你是不是已经豁然开朗了呢?开头的问题中,从 returnsError
返回的 error
接口类型变量 err
的数据指针虽然为空,但它的类型信息(iface.tab
)并不为空,而是 *MyError
对应的类型信息,这样 err
与 nil
(0x0,0x0)相比自然不相等,这就是我们开头那个问题的答案解析,现在你明白了吗?
4. 空接口类型变量与非空接口类型变量的等值比较
下面是非空接口类型变量和空接口类型变量之间进行比较的例子:
func printEmptyInterfaceAndNonEmptyInterface() {
var eif interface{} = T(5)
var err error = T(5)
println("eif:", eif)
println("err:", err)
println("eif = err:", eif == err)
err = T(6)
println("eif:", eif)
println("err:", err)
println("eif = err:", eif == err)
}
这个示例的输出结果如下:
eif: (0x10b3b00,0x10eb4d0)
err: (0x10ed380,0x10eb4d8)
eif = err: true
eif: (0x10b3b00,0x10eb4d0)
err: (0x10ed380,0x10eb4e0)
eif = err: false
你可以看到,空接口类型变量和非空接口类型变量内部表示的结构有所不同(第一个字段:_type vs. tab),两者似乎一定不能相等。但 Go
在进行等值比较时,类型比较使用的是 eface
的 _type
和 iface
的 tab._type
,因此就像我们在这个例子中看到的那样,当 eif
和 err
都被赋值为T(5)时,两者之间是划等号的。
参考:https://time.geekbang.org/column/article/473414