小编典典

隐藏nil值,了解golang为什么会失败

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()
  }
}

我发现检查该值是否实际的唯一方法nil是使用反射。

这真的是想要的行为吗?还是在代码中看不到一些重大错误?

在此处播放链接


阅读 374

收藏
2020-07-02

共1个答案

小编典典

这里的问题是那shower是一种interface类型。Go中的接口类型保存实际值及其 动态
类型。关于此的更多详细信息:反射定律#接口的表示

您返回的切片包含2个非nil值。第二个值是一个接口值,一个保存nil指针值的(值;类型)对和一个*display具体类型。从Go语言规范中引用:比较运算符

接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者两个值都具有value,则它们是相等的nil

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

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

这似乎不可行,因为您必须知道接口所持有的实际类型。但是请注意,您可以使用反射来判断非nil接口值是否使用来包装nilValue.IsNil()。您可以在Go
Playground
上看到一个示例。

为什么以这种方式实施?

与其他具体类型不同的接口(非接口)可以容纳不同具体类型的值(不同的静态类型)。运行时需要知道存储在接口类型变量中的值的动态类型或运行时类型。

An
interface只是一个方法集,如果相同方法属于该类型的方法集的一部分,则
任何类型都可以
实现它。有些类型不能为,例如或为其基础类型的自定义类型。在这些情况下,您将不需要能够存储该特定类型的值。nil``struct``int``nil

但是 任何类型
还包括nil有效值(例如切片,地图,通道,所有指针类型)的具体类型,因此,为了在运行时存储满足接口要求的值,可以合理地支持nil在接口内部进行存储。但是除了nil接口内部,我们还必须存储其动态类型,因为该nil值不包含此类信息。nil当要存储在其中的值是时,替代选项将用作接口值本身nil,但是此解决方案不足,因为它将丢失动态类型信息。

有人说Go的接口是动态类型的,但这是误导的。它们是静态类型的:接口类型的变量始终具有相同的静态类型,即使在运行时存储在接口变量中的值可能会更改类型,该值也将始终满足接口的要求。

通常,如果要指示类型nil的值interface,请使用显式nil值,然后可以测试是否nil相等。最常见的示例是内置error类型,它是一种方法的接口。只要没有错误,就可以显式设置或返回值,nil而不是某些具体(非接口)类型的错误变量的值(这实际上是错误的做法,请参见下面的演示)。

在您的示例中,混淆源于以下事实:

  • 您想要一个值作为接口类型(shower
  • 但是您要存储在切片中的值不是类型shower而是具体类型

因此,当您将一个*display类型放入shower切片中时,将创建一个接口值,该接口值是一对(value;
type),其中value是nil且type是*display。该对中的
将是nil,而不是接口值本身。如果您将一个nil值放入分片中,则接口值 本身nil,条件x == niltrue

示范

请参见以下示例:操场

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值和该类型*MyErr,因此该接口值不是nil

2020-07-02