最新文章: 官方的讨论: Why is my nil error value not equal to nil?
最近在项目中踩了一个深坑——“Golang中一个包含nil指针的接口不是nil接口”,总结下分享出来,如果你不是很理解这句话,那推荐认真看下下面的示例代码,避免以后写代码时踩坑。
先一起来看下这段代码,你感觉有没有问题呢?
type IPeople interface {
hello()
}
type People struct {
}
func (p *People) hello() {
fmt.Println("github.com/meetbetter")
}
func errFunc1(in int) *People {
if in == 0 {
fmt.Println("importantFunc返回了一个nil")
return nil
} else {
fmt.Println("importantFunc返回了一个非nil值")
return &People{}
}
}
func main() {
var i IPeople
in := 0
i = errFunc1(in)
if i == nil {
fmt.Println("哈,外部接收到也是nil")
} else {
fmt.Println("咦,外部接收到不是nil哦")
fmt.Printf("%v, %T\n", i, i)
}
}
复制代码
这段代码的执行结果是:
importantFunc返回了一个nil
咦,外部接收到不是nil哦
<nil>, *main.People
复制代码
可以看到在main函数中收到的返回值不是nil, 明明在errFunc1()函数中返回的是nil,到了main函数为什么收到的不是nil呢? 这是因为:将nil赋值给*People
后再将*People
赋值给interface,*People
本身是是个指向nil的指针,但是将其赋给接口时只是接口中的值为nil,但是接口中的类型信息为*main.People
而不是nil,所以这个接口不是nil。 是的,Golang中的interface类型包含两部分信息——值信息和类型信息,只有interface的值合并类型都为nil时interface才为nil,interface底层实现可以在后面的源码分析看到。
先来看看正确的处理接口返回值的方法,是直接将nil赋给interface:
func rightFunc(in int) IPeople {
if in == 0 {
fmt.Println("importantFunc返回了一个nil")
return nil
} else {
fmt.Println("importantFunc返回了一个非nil值")
return &People{}
}
}
复制代码
下面的代码更清晰的证明了一个包含nil指针的接口不是nil接口
的结论:
type IPeople interface {
hello()
}
type People struct {
}
func (p *People) hello() {
fmt.Println("github.com/meetbetter")
}
func errFunc() *People {
var p *People
return p
}
func rightFunc() IPeople {
var p *People
return p
}
func main() {
if errFunc() == nil {
fmt.Println("对了哦,外部接收到也是nil")
} else {
fmt.Println("错了咦,外部接收到不是nil哦")
}
if rightFunc() == nil {
fmt.Println("对了哦,外部接收到也是nil")
} else {
fmt.Println("错了咦,外部接收到不是nil哦")
}
}
复制代码
输出结果:
对了哦,外部接收到也是nil
错了咦,外部接收到不是nil哦
复制代码
下面的注释信息来自参考文章中,从interface底层实现可以看出iface比eface 中间多了一层itab结构, itab 存储_type信息和[]fun方法集,所以即使data指向了nil 并不代表interface 就是nil, 还要考虑_type信息。
type eface struct {
_type *_type
data unsafe.Pointer
}
type iface struct {
tab *itab
data unsafe.Pointer
}
type _type struct {
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
bad int32
inhash int32
fun [1]uintptr
}
复制代码
2019-12-13补充
下面代码段:
package test
import "testing"
type Intf interface {
Write(string)
}
type T struct {}
func (t *T) Write(str string) {}
func CreateT() *T { return nil }
func CreateIntf() Intf { return nil }
func TestInterfaceNil(t *testing.T) {
// 方式1
t1 := CreateT()
t.Log(t1 == nil)
// 方式2
var t2 *T
t2 = CreateT()
t.Log(t2 == nil)
// 方式3
var t3 Intf
t3 = CreateT()
t.Log(t3 == nil) // 结果与预期不一致,
// 为什么会这样?
// 其实一个interface{}类型包含两个指针, 一个指向值类型一个指向值内容
// 如果定义了一个interface{}类型, 如同定义了一个结构体一样, 会初始化内部数据的指向.
//当我们将一个具体类型的值赋值给一个interface类型的变量的时候,就同时把类型和值都赋值给了interface里的两个指针。如果这个具体类型的值是nil的话,interface变量依然会存储对应的类型指针和值指针。
var t4 Intf
t4 = CreateIntf()
t.Log(t4 == nil)
}
如果外层定义了一个接口类型变量, 会自动初始化
inerface
内部的指针, 如果函数返回的是接口的具体实现类型, 那个go语言会自动使用nil concrete-typed value
而非nil inerface value
, 所以在方式3
中判断是t3 != nil
, 如果函数返回的就是接口类型, 那么go语言会将返回值作为nil intface value
赋值, 所以, 此时t4 == nil
.
以上完整代码均整理在Github-跟着示例代码学Golang项目。
参考文章: Golang第一大坑
原网址: 访问