在这种情况下,我无法理解如何正确确保某些内容不是nil
:
package main
type shower interface {
getWater() []shower
}
type display struct {
SubDisplay *display
}
func (d display) getWater() []shower {
return []shower{display{}, d.SubDisplay}
}
func main() {
// SubDisplay will be initialized with null
s := display{}
// water := []shower{nil}
water := s.getWater()
for _, x := range water {
if x == nil {
panic("everything ok, nil found")
}
//first iteration display{} is not nil and will
//therefore work, on the second iteration
//x is nil, and getWater panics.
x.getWater()
}
}
我发现检查该值是否实际上是
nil
的唯一方法是使用反射。这真的是想要的行为吗?还是在代码中看不到一些重大错误?
Play link here
最佳答案
这里的问题是shower
是interface
类型。 Go中的接口类型保存实际值及其动态类型。有关此的更多详细信息:The Laws of Reflection #The representation of an interface。
您返回的 slice 包含2个非nil
值。第二个值是一个接口值,一个(值;类型)对,它保存一个nil
指针值和一个*display
具体类型。引用Go Language Specification: Comparison operators:
接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者两个接口值都具有nil
,则它们相等。
因此,如果将其与nil
进行比较,则为false
。如果将其与表示对(nil;*display)
的接口值进行比较,则将为true
:
if x == (*display)(nil) {
panic("everything ok, nil found")
}
这似乎不可行,因为您必须知道接口所持有的实际类型。但是请注意,您可以使用反射来判断非
nil
接口值是否使用 nil
包装了Value.IsNil()
值。您可以在Go Playground上看到一个示例。为什么用这种方式实现?
与其他具体类型不同的接口(非接口)可以容纳不同具体类型的值(不同的静态类型)。运行时需要知道存储在接口类型变量中的值的动态或运行时类型。
interface
只是方法集,如果相同方法是该类型的method set的一部分,则任何类型都可以实现它。有些类型不能为nil
,例如struct
或以int
作为其基础类型的自定义类型。在这些情况下,您将不需要能够存储该特定类型的nil
值。但是任何类型还包括
nil
是有效值的具体类型(例如, slice , map ,通道,所有指针类型),因此为了在运行时存储满足接口要求的值,合理的做法是将nil
存储在接口内部。但是除了接口内的nil
外,我们还必须存储其动态类型,因为nil
值不包含此类信息。另一种选择是当要存储在其中的值是nil
时,使用nil
作为接口值本身,但是此解决方案不足,因为它将丢失动态类型信息。有人说Go的接口是动态类型的,但这会产生误导。它们是静态类型的:接口类型的变量始终具有相同的静态类型,即使在运行时存储在接口变量中的值可能会更改类型,该值也将始终满足接口的要求。
通常,如果要为
nil
类型的值指示interface
,请使用显式的nil
值,然后可以测试nil
的相等性。最常见的示例是内置的 error
类型,它是一种方法的接口。只要没有错误,就可以显式设置或返回值nil
,而不是某些具体(非接口)类型的错误变量的值(这是非常糟糕的做法,请参见下面的演示)。在您的示例中,混淆源于以下事实:
想要将值作为接口类型(
shower
)的shower
类型,而是具体类型因此,当您将
*display
类型放入shower
slice 时,将创建一个接口值,该接口值是一对(value; type),其中value为nil
和type为*display
。该对中的值将是nil
,而不是接口值本身。如果将nil
值放入 slice 中,则接口值本身将是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 // This is the trivial true case
case 1:
var p *MyErr
return p // This will be false
case 2:
return (*MyErr)(nil) // Same as case 1
case 3:
var err error // Zero value is nil for the interface
return err // This will be true because err is already interface type
case 4:
var p *MyErr
return error(p) // This will be false because the interface points to a
// nil item but is not nil itself.
}
}
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> true
4 <nil> false
在情况2中,返回了
nil
指针,但首先将其转换为接口类型(error
),因此创建了一个接口值,其中包含一个nil
值和*MyErr
类型,因此该接口值不是nil
。