为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
郎朗坤 2021-09-21 20:50:13
我无法理解如何正确确保nil在这种情况下不是:package maintype 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是使用反射。这真的是想要的行为吗?或者我没有在我的代码中看到一些重大错误?
查看完整描述

3 回答

?
倚天杖

TA贡献1828条经验 获得超3个赞

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


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


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


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


if x == (*display)(nil) {

    panic("everything ok, nil found")

}

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


为什么以这种方式实施?


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


Aninterface只是一个方法集,如果相同的方法是该类型的方法集的一部分,则任何类型都会实现它。有些类型不能是,例如 a或自定义类型作为其基础类型。在这些情况下,您不需要能够存储该特定类型的值。nilstructintnil


但是任何类型也包括其中nil是有效值的具体类型(例如切片、映射、通道、所有指针类型),因此为了在运行时存储满足接口的值,支持存储nil在接口内部是合理的。但是除了nil接口内部,我们必须存储它的动态类型,因为nil值不携带此类信息。另一种选择是nil当要存储在其中的值是 时,将其本身用作接口值nil,但这种解决方案是不够的,因为它会丢失动态类型信息。


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


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


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


您希望将值作为接口类型 ( shower)

但是您要存储在切片中的值不是类型shower而是具体类型

因此,当您将*display类型放入shower切片时,将创建一个接口值,它是一对 (value;type) 其中 value isnil和 type is *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 // == nil

    case 1:

        var p *MyErr

        return p // != nil

    case 2:

        return (*MyErr)(nil) // != nil

    case 3:

        var p *MyErr

        return error(p) // != nil because the interface points to a

                        // nil item but is not nil itself.

    case 4:

        var err error // == nil: zero value is nil for the interface

        return err    // This will be true because err is already interface type

    }

}


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> false

4 <nil> true

在第 2 种情况下,nil返回一个指针,但首先将其转换为接口类型 ( error),因此会创建一个接口值,该nil值包含一个值和类型*MyErr,因此接口值不是nil。


查看完整回答
反对 回复 2021-09-21
?
qq_笑_17

TA贡献1818条经验 获得超7个赞

让我们把接口想象成一个指针。


假设你有一个指针a,它是零,指向什么。


var a *int // nil

然后你有一个指针b,它指向a.


var b **int

b = &a // not nil

看看发生了什么?b指向一个不指向任何内容的指针。因此,即使它是链末端的 nil 指针,b也确实指向某些东西 - 它不是 nil。


如果您查看进程的内存,它可能如下所示:


address | name | value

1000000 | a    | 0

2000000 | b    | 1000000

看?a指向地址 0(这意味着它是nil),并b指向a(1000000)的地址。


这同样适用于接口(除了它们在内存中看起来有点不同)。


像指针一样,指向 nil 指针的接口本身不会是 nil。



查看完整回答
反对 回复 2021-09-21
?
小怪兽爱吃肉

TA贡献1852条经验 获得超1个赞

通过提供您正在寻找的确切答案,我将采用另一种方式来回答您的具体问题:


更换支票:


if x == nil {

   panic("everything ok, nil found")

}

和:


if _, ok := x.(display); !ok {

   panic("everything ok, nil found")

}

这里的想法是我们试图将界面类型(淋浴)转换为具体类型的显示。显然第二个切片项目(d.SubDisplay)不是。


查看完整回答
反对 回复 2021-09-21
  • 3 回答
  • 0 关注
  • 188 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信