我无法理解如何正确地确保在这种情况下某些东西不是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
的唯一方法是使用反射。
这真的是想要的行为吗?或者我没有在代码中看到一些重大错误?
答案 0 :(得分:58)
此处的问题是shower
是interface
类型。 Go中的接口类型包含实际值及其动态类型。有关此内容的更多详细信息:The Laws of Reflection #The representation of an interface。
您返回的切片包含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
接口值是否使用Value.IsNil()
包装nil
值。您可以在Go Playground上看到此示例。
为什么以这种方式实施?
与其他具体类型(非接口)不同,接口可以包含不同具体类型(不同的静态类型)的值。运行时需要知道存储在接口类型变量中的值的动态或运行时类型。
interface
只是一个方法集,任何类型实现它,如果相同的方法是该类型的method set的一部分。有些类型不能nil
,例如struct
或自定义类型int
作为其基础类型。在这些情况下,您无需存储该特定类型的nil
值。
但任何类型还包括具体类型,其中nil
是有效值(例如切片,映射,通道,所有指针类型),因此为了在运行时存储该值满足接口,支持在接口中存储nil
是合理的。但除了接口内的nil
之外,我们必须存储其动态类型,因为nil
值不包含此类信息。替代选项是当要存储在其中的值为nil
时使用nil
作为接口值,但此解决方案不足以因为它将丢失动态类型信息。
有些人说Go的接口是动态类型的,但这是误导性的。它们是静态类型的:接口类型的变量始终具有相同的静态类型,即使在运行时存储在接口变量中的值可能会更改类型,该值也始终满足接口。
通常,如果您要为nil
类型指定interface
,请使用显式nil
值,然后您可以测试nil
相等性。最常见的示例是内置error
类型,它是一个方法的接口。只要没有错误,您就显式设置或返回值nil
而不是某个具体(非接口)类型错误变量的值(这将是非常糟糕的做法,请参阅下面的演示)。
在你的例子中,混淆来自以下事实:
shower
)shower
类型,而是具体类型因此,当您将*display
类型放入shower
切片时,将创建一个接口值,该值是一对(值;类型),其中值为nil
并键入是*display
。该对中的值将为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 // 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
值和类型{的接口值{1}},因此界面值不是*MyErr
。
答案 1 :(得分:7)
让我们将接口视为指针。
假设你有一个指针a
并且它是零,指向什么都没有。
var a *int // nil
然后你有一个指针b
,它指向a
。
var b **int
b = &a // not nil
看看发生了什么? b
指向一个指向任何东西的指针。因此,即使它是链末尾的零指针,b
也指向某个东西 - 它不是零。
如果您要查看进程的内存,它可能如下所示:
address | name | value
1000000 | a | 0
2000000 | b | 1000000
请参阅? a
指向地址0(表示nil
},b
指向a
(1000000)的地址。
这同样适用于接口(除了它们看起来有点不同in memory)。
像指针一样,指向nil指针的接口本身不会为nil 。
在此处,请亲眼看看how this works with pointers和how it works with interfaces。
答案 2 :(得分:1)
我将通过提供您一直在寻找的确切答案来替代您的具体问题:
替换支票:
if x == nil {
panic("everything ok, nil found")
}
具有:
if _, ok := x.(display); !ok {
panic("everything ok, nil found")
}
这里的想法是我们试图将接口类型(淋浴器)转换为具体的类型显示。显然第二个切片项目(d.SubDisplay)不是。