隐藏零值,了解为什么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
是一个interface
types。 Go中的Intefacetypes保存实际值及其dynamictypes。 有关这方面的更多细节: reflection的规律#接口的表示 。
您返回的切片包含2个非零值。 第二个值是一个接口值,一个保存nil
值和*display
types的(值;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的方法集的一部分。 有不能nil
types,例如一个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,该值将总是满足接口。
一般情况下,如果要为interface
types的值指示nil
,请使用显式nil
值,然后可以testingnil
相等性。 最常见的例子是内置的error
types,这是一种方法的接口。 每当没有错误,你明确地设置或返回值nil
而不是一些具体(非接口)types错误variables的值(这将是非常糟糕的做法,请参见下面的演示)。
在你的例子中,混淆来自事实:
- 你想有一个接口types的价值(
shower
) - 但是你想要存储在切片中的价值不是
shower
而是一个具体的types
所以当你把一个*display
types放到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)的地址。
接口也一样(除了它们在内存上看起来有点不同)。
就像一个指针一样,指向一个零指针的接口本身不会是零 。
在这里,看看自己如何使用指针以及它如何与接口一起工作 。