zoukankan      html  css  js  c++  java
  • Go“一个包含nil指针的接口不是nil接口”踩坑

    最近在项目中踩了一个深坑——“Golang中一个包含nil指针的接口不是nil接口”,现象是函数内返回了nil给一个对象,使用interface接收函数返回值判断始终不为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
    ", 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")
    }
    
    //错误:将nil的people给空接口后接口就不为nil,因为interface中的value为nil但type不为nil
    
    func errFunc() *People {
    
    	return nil
    }
    
    //正确处理返回nil给接口的方法,返回时go就确定了接口是不是nil
    func rightFunc() IPeople {
    
    	return nil
    }
    func main() {
    
    	var i IPeople
    	i = errFunc()
    	if i == nil { //想通过接口是否为nil来判断故障,却始终判断接口非空
    
    		fmt.Println("errFunc对了哦,外部接收到也是nil")
    		fmt.Println(reflect.TypeOf(i))
    	} else {
    
    		fmt.Println("errFunc错了咦,外部接收到不是nil哦")
    		fmt.Println(reflect.TypeOf(i))
    	}
    
    	i = rightFunc()
    	if i == nil {
    
    		fmt.Println("rightFunc对了哦,外部接收到也是nil")
    		fmt.Println(reflect.TypeOf(i))
    	} else {
    
    		fmt.Println("rightFunc错了咦,外部接收到不是nil哦")
    		fmt.Println(reflect.TypeOf(i))
    
    	}
    
    }
    

    输出结果:

    errFunc错了咦,外部接收到不是nil哦
    *main.People
    rightFunc对了哦,外部接收到也是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      tflag
        align      uint8    //对齐
        fieldalign uint8    //嵌入结构体时的对齐
        kind       uint8    //kind 有些枚举值kind等于0是无效的
        alg        *typeAlg //函数指针数组,类型实现的所有方法
        gcdata    *byte
        str       nameOff
        ptrToThis typeOff
    }
    type itab struct {
        inter  *interfacetype  //接口类型
        _type  *_type          //结构类型
        link   *itab
        bad    int32
        inhash int32
        fun    [1]uintptr      //可变大小 方法集合
    }
    

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

    参考文章:

    Golang第一大坑

    "一个包含nil指针的接口不是nil接口"的讨论

  • 相关阅读:
    Linux查找日志技巧
    win10编辑hosts文件提示没有权限
    Viso 2019 下载与激活
    SpringBoot日志记录组件logback的配置解释
    webservice(axis)接口上传文件附件 及 用zlib解压缩
    axis2添加接口过程
    关于“finally block does not complete normally”警告提示
    webservice介绍与流程(杂)
    JDBC理解
    Myeclipse中调整xml中字体显示大小
  • 原文地址:https://www.cnblogs.com/CodeWithTxT/p/11297300.html
Copyright © 2011-2022 走看看