sha*_*ner 44 null pointers interface go
我无法理解如何正确地确保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()
}
}
Run Code Online (Sandbox Code Playgroud)
我发现检查该值是否实际的唯一方法nil是使用反射.
这真的是想要的行为吗?或者我没有在代码中看到一些重大错误?
icz*_*cza 60
这里的问题是这shower是一种interface类型.Go中的接口类型包含实际值及其动态类型.关于此的更多细节:反射定律#界面的表示.
您返回的切片包含2个非nil值.第二个值是一个接口值,一个包含nil值和*display类型的(值;类型)对.引用Go语言规范:比较运算符:
接口值具有可比性.如果两个接口值具有相同的动态类型和相同的动态值,或者两者都具有值,则它们是相等的
nil.
所以,如果你把它比作nil,这将是false.如果将它与表示该对的接口值进行比较(nil;*display),它将是true:
if x == (*display)(nil) {
panic("everything ok, nil found")
}
Run Code Online (Sandbox Code Playgroud)
这似乎不可行,因为您必须知道接口所具有的实际类型.
为什么这样实现?
与其他具体类型(非接口)不同的接口可以包含不同具体类型(不同静态类型)的值.运行时需要知道存储在接口类型变量中的值的动态或运行时类型.
An nil只是一个方法集,如果相同的方法是该类型的方法集的一部分,任何类型都会实现它.存在不能的类型,例如具有其基础类型的自定义类型.在这些情况下,您不需要能够存储该特定类型的值.nilValue.IsNil()interfacenil
但是任何类型还包括具体类型,其中struct是有效值(例如切片,映射,通道,所有指针类型),因此为了在运行时存储满足接口的值,支持int在接口内存储是合理的.但除了nil界面内部,我们必须存储其动态类型,因为nil值不包含这些信息.备用选项是nil当要存储在其中的值时,将其用作接口值本身nil,但此解决方案不足以因为它将丢失动态类型信息.
有人说Go的接口是动态类型的,但这是误导性的.它们是静态类型的:接口类型的变量始终具有相同的静态类型,即使在运行时存储在接口变量中的值可能会更改类型,该值也始终满足接口.
通常,如果要指示类型nil的值nil,请使用显式nil值,然后可以测试nil相等性.最常见的示例是内置interface类型,它是一个方法的接口.只要没有错误,您就显式设置或返回值nil而不是某些具体(非接口)类型错误变量的值(这将是非常糟糕的做法,请参见下面的演示).
在你的例子中,混淆源于以下事实:
nil)error而是具体类型因此,当您将一个nil类型放入shower切片时,将创建一个接口值,这是一对(value; type),其中value是shower,type是*display.该对内的值将是shower,而不是接口值本身.如果你会把一个nil值到片,则接口值本身是*display和条件nil会nil.
示范
看这个例子:游乐场
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)
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
0 <nil> true
1 <nil> false
2 <nil> false
3 <nil> true
4 <nil> false
Run Code Online (Sandbox Code Playgroud)
在情况2 nil中,返回一个指针,但首先它被转换为接口类型(x == nil),因此创建了一个包含true值和类型nil的接口值,因此接口值不是error.
让我们将接口视为指针.
假设你有一个指针a而且它是零,指向什么都没有.
var a *int // nil
Run Code Online (Sandbox Code Playgroud)
然后你有一个指针b,它指向a.
var b **int
b = &a // not nil
Run Code Online (Sandbox Code Playgroud)
看看发生了什么?b指向一个指向任何东西的指针.因此即使它是链末端的零指针,b也指向某事 - 它不是零.
如果您查看进程的内存,它可能如下所示:
address | name | value
1000000 | a | 0
2000000 | b | 1000000
Run Code Online (Sandbox Code Playgroud)
看到?a指向地址0(表示它nil),并b指向地址a(1000000).
这同样适用于接口(除了它们在内存中看起来有点不同).
像指针一样,指向nil指针的接口本身不会为nil.
在这里,了解它如何使用指针以及它如何与接口一起工作.