在这种情况下,我无法理解如何正确确保某些内容不是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的唯一方法是使用反射。

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

Play link here

最佳答案

这里的问题是showerinterface类型。 Go中的接口类型保存实际值及其动态类型。有关此的更多详细信息:The Laws of Reflection #The representation of an interface

您返回的 slice 包含2个非nil值。第二个值是一个接口值,一个(值;类型)对,它保存一个nil指针值和一个*display具体类型。引用Go Language Specification: Comparison operators:

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

因此,如果将其与nil进行比较,则为false。如果将其与表示对(nil;*display)的接口值进行比较,则将为true:

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

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

为什么用这种方式实现?

与其他具体类型不同的接口(非接口)可以容纳不同具体类型的值(不同的静态类型)。运行时需要知道存储在接口类型变量中的值的动态或运行时类型。
interface只是方法集,如果相同方法是该类型的method set的一部分,则任何类型都可以实现它。有些类型不能为nil,例如struct或以int作为其基础类型的自定义类型。在这些情况下,您将不需要能够存储该特定类型的nil值。

但是任何类型还包括nil是有效值的具体类型(例如, slice , map ,通道,所有指针类型),因此为了在运行时存储满足接口要求的值,合理的做法是将nil存储在接口内部。但是除了接口内的nil外,我们还必须存储其动态类型,因为nil值不包含此类信息。另一种选择是当要存储在其中的值是nil时,使用nil作为接口值本身,但是此解决方案不足,因为它将丢失动态类型信息。

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

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

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

想要将值作为接口类型(shower)的
  • ,但您要存储在 slice 中的值不是shower类型,而是具体类型

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

    示范

    请参见以下示例:Playground
    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

    09-15 21:20