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

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和条件nilnil.

示范

看这个例子:游乐场

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.

  • nil指针不是无效值.请注意,您可以在nil指针上调用方法就好了:http://play.golang.org/p/Agd8eIwaKZ尝试阅读关于nil指针和nil接口的帖子,它可能会有所帮助:http://npf.io/2014/ 05 /介绍,到去接口/#toc_4 (7认同)
  • 没有什么令人震惊的.你返回一个两个接口值的片段,都是非零的.其中一个非零接口值包含nil值.接口值_has_为非零以包含任何内容,甚至是零.你可以像icza建议的那样修复它,或重新设计你的API,例如不返回一片接口. (5认同)
  • @sharpner您可以提供一个使用/返回接口类型值的库.但如果值"缺失",则明确返回"nil". (2认同)

Mos*_*vah 9

让我们将接口视为指针.

假设你有一个指针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.

在这里,了解它如何使用指针以及它如何与接口一起工作.