var val interface{} = (interface{})(nil)
val的类型是interface{},值是nil
var val interface{} = nil
val的类型和值都是nil
ackage main
import (
“fmt”
)
func main() {
var val interface{} = nil
if val == nil {
fmt.Println(“val is nil”)
} else {
fmt.Println(“val is not nil”)
}
}
变量val是interface类型,它的底层结构必然是(type, data)。由于nil是untyped(无类型),而又将nil赋值给了变量val,所以val实际上存储的是(nil, nil)。因此很容易就知道val和nil的相等比较是为true的。
$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
val is nil
对于将任何其它有意义的值类型赋值给val,都导致val持有一个有效的类型和数据。也就是说变量val的底层结构肯定不为(nil, nil),因此它和nil的相等比较总是为false。
上面的讨论都是在围绕值类型来进行的。在继续讨论之前,让我们来看一种特例:(*interface{})(nil)。将nil转成interface类型的指针,其实得到的结果仅仅是空接口类型指针并且它指向无效的地址。注意是空接口类型指针而不是空指针,这两者的区别蛮大的,学过C的童鞋都知道空指针是什么概念。
关于(interface{})(nil)还有一些要注意的地方。这里仅仅是拿(interface{})(nil)来举例,对于(int)(nil)、(byte)(nil)等等来说是一样的。上面的代码定义了接口指针类型变量val,它指向无效的地址(0x0),因此val持有无效的数据。但它是有类型的(interface{})。所以val的底层结构应该是:(interface{}, nil)。有时候您会看到(interface{})(nil)的应用,比如var ptrIface = (interface{})(nil),如果您接下来将ptrIface指向其它类型的指针,将通不过编译。或者您这样赋值:*ptrIface = 123,那样的话编译是通过了,但在运行时还是会panic的,这是因为ptrIface指向的是无效的内存地址。其实声明类似ptrIface这样的变量,是因为使用者只是关心指针的类型,而忽略它存储的值是什么。还是以例子来说明:
package main
import (
“fmt”
)
func main() {
var val interface{} = (interface{})(nil)
// val = (int)(nil)
if val == nil {
fmt.Println(“val is nil”)
} else {
fmt.Println(“val is not nil”)
}
}
很显然,无论该指针的值是什么:(*interface{}, nil),这样的接口值总是非nil的,即使在该指针的内部为nil。
$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
val is not nil
interface类型的变量和nil的相等比较出现最多的地方应该是error接口类型的值与nil的比较。有时候您想自定义一个返回错误的函数来做这个事,可能会写出以下代码:
package main
import (
“fmt”
)
type data struct{}
func (this *data) Error() string { return “” }
func test() error {
var p *data = nil
return p
}
func main() {
var e error = test()
if e == nil {
fmt.Println(“e is nil”)
} else {
fmt.Println(“e is not nil”)
}
}
但是很可惜,以上代码是有问题的。
$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
e is not nil
我们可以来分析一下。error是一个接口类型,test方法中返回的指针p虽然数据是nil,但是由于它被返回成包装的error类型,也即它是有类型的。所以它的底层结构应该是(*data, nil),很明显它是非nil的。
可以打印观察下底层结构数据:
package main
import (
“fmt”
“unsafe”
)
type data struct{}
func (this *data) Error() string { return “” }
func test() error {
var p *data = nil
return p
}
func main() {
var e error = test()
d := (*struct {
itab uintptr
data uintptr
})(unsafe.Pointer(&e))
fmt.Println(d) } $ cd $GOPATH/src/interface_test $ go build $ ./interface_test &{3078907912 0} 正确的做法应该是:
package main
import (
“fmt”
)
type data struct{}
func (this *data) Error() string { return “” }
func bad() bool {
return true
}
func test() error {
var p *data = nil
if bad() {
return p
}
return nil
}
func main() {
var e error = test()
if e == nil {
fmt.Println(“e is nil”)
} else {
fmt.Println(“e is not nil”)
}
}