Go“一个包含nil指针的接口不是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 IPeoplein := 0i = errFunc1(in)if i == nil {    fmt.Println("哈,外部接收到也是nil")} else {    fmt.Println("咦,外部接收到不是nil哦")    fmt.Printf("%v, %T
", i, i)}

}
这段代码的执行结果是:

importantFunc返回了一个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")

}

//错误:将nil的people给空接口后接口就不为nil,因为interface中的value为nil但type不为nil
func errFunc() *People {

var p *Peoplereturn p

}

//正确处理返回nil给接口的方式:直接将nil赋给interface
func rightFunc() IPeople {

var p *Peoplereturn 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底层实现
下面的注释信息来自参考文章中,从interface底层实现可以看出iface比eface 中间多了一层itab结构, itab 存储_type信息和[]fun方法集,所以即使data指向了nil 并不代表interface 就是nil, 还要考虑_type信息。

type eface struct { //空接口

_type *_type         //类型信息data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)

}
type iface struct { //带有方法的接口

tab  *itab           //存储type信息还有结构实现方法的集合data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)

}
type _type struct {

size       uintptr  //类型大小ptrdata    uintptr  //前缀持有所有指针的内存大小hash       uint32   //数据hash值tflag      tflagalign      uint8    //对齐fieldalign uint8    //嵌入结构体时的对齐kind       uint8    //kind 有些枚举值kind等于0是无效的alg        *typeAlg //函数指针数组,类型实现的所有方法gcdata    *bytestr       nameOffptrToThis typeOff

}
type itab struct {

inter  *interfacetype  //接口类型_type  *_type          //结构类型    *itabbad    int32inhash int32fun    [1]uintptr      //可变大小 方法集合

}
以上完整代码均整理在Github-跟着示例代码学Golang项目。

参考文章:
Golang第一大坑

"一个包含nil指针的接口不是nil接口"的讨论
原文地址https://www.cnblogs.com/CodeWithTxT/p/11297300.html

收藏 打印