问题在于
shower
是一个接口类型。在Go中,接口类型保存了实际值及其动态类型的信息。更多详细信息请参见
反射定律#接口的表示。
你返回的切片包含2个非
nil
值。第二个值是一个接口值,即一个(value;type)对,其中保存了一个
nil
指针值和一个
*display
具体类型。引用自
Go语言规范:比较运算符:
接口值可以进行比较。如果两个接口值具有相同的动态类型和相等的动态值,或者两个接口值都具有值nil
,则它们相等。
因此,如果你将其与
nil
进行比较,结果将为
false
。如果你将其与表示为
(nil;*display)
的接口值进行比较,结果将为
true
:
if x == (*display)(nil) {
panic("everything ok, nil found")
}
这似乎是不可行的,因为您需要知道接口所持有的实际类型。但请注意,您可以使用反射来判断非
nil
接口值是否包装了一个
nil
值,使用
Value.IsNil()
。您可以在
Go Playground上看到此示例。
为什么要这样实现?
与其他具体类型(非接口)不同,接口可以容纳不同静态类型(不同具体类型)的值。运行时需要知道存储在接口类型变量中的值的动态或运行时类型。
一个
interface
只是一个方法集合,如果相同的方法是类型
方法集的一部分,则任何类型都可以实现它。有些类型不能为
nil
,例如具有
int
作为其基础类型的
struct
或自定义类型。在这些情况下,您不需要能够存储该特定类型的
nil
值。
但是
任何类型也包括
nil
是有效值的具体类型(例如切片、映射、通道和所有指针类型),因此为了在运行时存储满足接口的值,支持在接口中存储
nil
是合理的。但是除了接口中的
nil
之外,我们必须将其动态类型存储为
nil
值不携带此类信息。另一种选择是在要存储在其中的值为
nil
时将
nil
用作接口值本身,但是这种解决方案是不充分的,因为它会丢失动态类型信息。
有些人说Go语言的接口是动态类型,但这是误导性的。它们是静态类型:接口类型的变量始终具有相同的静态类型,尽管在运行时存储在接口变量中的值可能会更改类型,但该值将始终满足接口的要求。
通常情况下,如果想要表示接口类型的值为
nil
,请使用明确的
nil
值,然后可以测试
nil
相等性。最常见的例子是内置的
error
类型,它是一个带有一个方法的接口。当没有错误时,您明确设置或返回
nil
值,而不是某个具体(非接口)类型的错误变量的值(这将是真正糟糕的做法,请参见下面的演示)。
在你的例子中混淆的原因在于:
- 你想将一个值作为接口类型(
shower
)
- 但你想要存储在切片中的值不是
shower
类型,而是具体类型。
当您将
*display
类型放入
shower
切片中时,将创建一个接口值,它是(value;type)的一对,其中value为
nil
,type为
*display
。对偶中的value将为
nil
,而不是接口值本身。如果将
nil
值放入切片中,则接口值本身将为
nil
,并且条件
x == nil
将为
true
。
演示:
请参见此示例:
Playground
type MyErr string
func (m MyErr) Error() string {
return "big fail"
}
func doSomething(i int) error {
switch i {
default:
return nil
case 1:
var p *MyErr
return p
case 2:
return (*MyErr)(nil)
case 3:
var p *MyErr
return error(p)
case 4:
var err error
return err
}
}
func main() {
for i := 0; i <= 4; i++ {
err := doSomething(i)
fmt.Println(i, err, err == nil)
}
}
输出:
0 <nil> true
1 <nil> false
2 <nil> false
3 <nil> false
4 <nil> true
在第二种情况下,将返回一个
nil
指针,但首先它会被转换为接口类型(
error
),因此创建了一个包含
nil
值和类型
*MyErr
的接口值,所以该接口值不是
nil
。