接口可以是 nil 类型,但这并不意味着它们是 nil?



我写了一段代码,实际上是这样的:

package main
import "fmt"
type SomeInterface interface {
Retrieve(identifier string)
}
type SomeStruct struct {}
func (r SomeStruct) Retrieve(identifier string) {
fmt.Println("identifier ", identifier)
}
type Handler struct {
Name string
SomeObject SomeInterface
}
func main() {
var someStruct *SomeStruct
var h = Handler{
Name: "helo",
SomeObject: someStruct,
}
fmt.Printf("before %+vrn", h.SomeObject)
if h.SomeObject == nil {
fmt.Printf("during %+vrn", h.SomeObject)
}
fmt.Printf("after %+vrn", h.SomeObject)
}

谁能给我解释一下为什么上面的输出是:

before <nil>
after <nil>

我一直在阅读关于nil类型的接口,但在这种情况下,我已经将接口分配给了一个尚未分配的指针,所以我会认为接口== nil和我会看到during <nil>-唉,情况并非如此。

接口值是一个简单的数据结构,由两部分组成:类型和底层值。因此,接口值本身可以是nil,或者接口值存在但底层值可以是nil。例如:

var x interface{} = nil // x is nil
var y interface{} = (interface{})(nil) // y is a interface{}, which *contains* nil

这在概念上与下面的区别有些类似:

var x []*int = nil // x is nil
var y []*int = []*int{nil} // y is a []*int, which *contains* nil

fmt.Printf模糊了接口的区别,因为它格式化了输出的方式;如果您愿意,可以使用反射更清楚地看到差异。

SomeObject不是nil,它只是指向一个为nil的SomeStruct。

我认为混乱是错误的。Printf在这种情况下打印<nil>,因为它在指针后面,最终结果是nil。

在Go语言中,引用某个接口实现的变量可以有多种类型。它的类型可以是<nil>(是的,nil既可以描述类型也可以描述值),它可以是它的一个实现者的类型,也可以是指向它的一个实现者的指针的类型。缺省情况下,引用接口的变量类型为nil。一旦你给它赋值了一些东西(除了nil本身),它就会采用你给它赋值的东西的类型(同样,要么是它的一个实现者的类型,要么是指向这些类型之一的指针)。

可以用%T打印接口变量的类型,用%v打印它的值:

func main() {
var i SomeInterface
fmt.Printf("%T, %vn", i, i) // Prints <nil>, <nil>

var someStruct SomeStruct
i = someStruct
fmt.Printf("%T, %vn", i, i) // Prints main.SomeStruct, {}

var someStructPtr *SomeStruct
i = someStructPtr
fmt.Printf("%T, %vn", i, i) // Prints *main.SomeStruct, <nil>
}

现在,无论何时比较h.SomeObject == nil,只有当两个操作数的类型和值匹配时,比较才会被计算为true。在您的示例中,h.SomeObject的值显然是<nil>(毕竟,someStruct的值肯定是<nil>,并且您将其值存储在h.SomeObject中)。根据我刚才的解释,h.SomeObject的类型应该是*SomeStructnil的值显然是<nil>.

但是,nil类型是什么?

嗯,nil可以接受许多类型,编译器必须决定每种用法应该采用哪种类型。当涉及到比较和赋值时,它只是接受被比较或赋值对象的类型。例如,如果将一个整型指针与nil进行比较,那么在这种情况下,nil的类型将是*int

但是所有这些必须在编译时决定,并且引用接口的变量可以在运行时更改类型。因此,当您将引用接口的变量与nil进行比较时,在这种情况下,编译器会给nil操作数提供什么类型?它给了它唯一一个合理的类型,<nil>

最后一个例子,考虑下面的代码:

func main() {
var p *SomeStruct = nil // = nil is optional; pointers default to nil
var i SomeInterface = p
printf("%tn", p == nil) // Prints true
printf("%tn", p == i) // Prints true
printf("%tn", i == nil) // Prints false
}

p == nil为true,因为p的类型为*SomeStruct,值为<nil>,而nil的类型也为*SomeStruct,值为<nil>

p == i是true,因为i也是类型为*SomeStruct,值为<nil>(它只是存储p的类型和值)。

但是,i == nil是假的,因为在本例中,nil的类型是<nil>而不是*SomeStruct

解决这个问题的方法很简单,除了nil本身,永远不在接口引用变量中存储值为<nil>的东西。这样,只要接口引用变量的值是<nil>,它的类型也将是<nil>,并且与nil的比较将按预期工作。由于这个原因,您经常会看到这样的代码:
if p == nil {
i = nil
} else {
i = p
}

最新更新