隐藏零值,了解为什么golang在这里失败

在这种情况下,我不明白如何正确地保证某些东西不是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的唯一方法是使用reflection。

这真的是想要的行为? 或者我没有看到我的代码中的一些重大错误?

在这里播放链接

这里的问题是shower是一个interfacetypes。 Go中的Intefacetypes保存实际值及其dynamictypes。 有关这方面的更多细节: reflection的规律#接口的表示 。

您返回的切片包含2个非零值。 第二个值是一个接口值,一个保存nil值和*displaytypes的(值;types)对。 Qutoing Go语言规范:比较操作符 :

接口值是可比的。 如果两个接口的值具有相同的dynamictypes和相同的dynamic值,或者两者的值均为nil则两个接口值相等。

所以如果你把它比作nil ,那就是false 。 如果您将它与代表对(nil;*display)的接口值进行比较,则会是true

 if x == (*display)(nil) { panic("everything ok, nil found") } 

这似乎是不可行的,因为你必须知道接口的实际types。

为什么这样实施?

与其他具体types(非接口)不同的接口可以保存不同具体types(不同的静态types)的值。 运行时需要知道存储在接口typesvariables中的值的dynamictypes或运行时types。

一个interface只是一个方法集, 任何types都可以实现它,如果相同的方法是types的方法集的一部分。 有不能niltypes,例如一个struct或一个自定义types,其int为底层types。 在这些情况下,您不需要能够存储该特定types的nil值。

但是任何types都包含nil是有效值的具体types(例如slice,maps,channels,all pointer types),所以为了在运行时存储满足接口的值,支持在接口中存储nil是合理的。 但除了接口里面的nil之外,我们必须存储它的dynamictypes,因为nil值不会携带这样的信息。 如果要存储的值是nil ,那么备用选项将使用nil作为接口值本身,但是这种解决scheme是不够的,因为它会丢失dynamictypes信息。

有人说Go的接口是dynamictypes的,但这是误导性的。 它们是静态types的:接口types的variables总是具有相同的静态types,并且即使在运行时存储在接口variables中的值可能改变types,该值将总是满足接口。

一般情况下,如果要为interfacetypes的值指示nil ,请使用显式nil值,然后可以testingnil相等性。 最常见的例子是内置的errortypes,这是一种方法的接口。 每当没有错误,你明确地设置或返回值nil而不是一些具体(非接口)types错误variables的值(这将是非常糟糕的做法,请参见下面的演示)。

在你的例子中,混淆来自事实:

  • 你想有一个接口types的价值( shower
  • 但是你想要存储在切片中的价值不是shower而是一个具体的types

所以当你把一个*displaytypes放到shower片中时,会创build一个界面值,这是一个值(value; type),其中value是nil ,type是*display 。 该对中的将是nil ,而不是接口值本身。 如果你将一个nil值放入切片,那么接口值本身将是nil并且条件x == nil将是true

示范

看这个例子: 游乐场

 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指针,但首先将其转换为接口types( error ),以便创build一个接口值,该接口值包含一个nil值和types*MyErr ,因此接口值不nil

让我们把接口看作一个指针。

说你有一个指针,它是零,没有指向任何东西。

 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指向(1000000)的地址。

接口也一样(除了它们在内存上看起来有点不同)。

就像一个指针一样,指向一个零指针的接口本身不会是零

在这里,看看自己如何使用指针以及它如何与接口一起工作 。