最近在项目中踩了一个深坑——“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\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")
} //错误:将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接口"的讨论

Go“一个包含nil指针的接口不是nil接口”踩坑的更多相关文章

  1. 【记录一个问题】golang神坑,明明返回了接口指针类型的nil值,却无法用if判断

    先看看导致异常的代码: package main import ( "fmt" "log" ) type MyError1 struct{ MyErrorCod ...

  2. 谈谈Delphi中的类和对象1---介绍几个概念 && 对象是一个地地道道的指针

    参考:http://blog.163.com/liang_liu99/blog/static/88415216200952123412180/ 以下的介绍主要针对的是Delphi的面向对象的知识,可能 ...

  3. go语言笔记——切片底层本质是共享数组内存!!!绝对不要用指针指向 slice切片本身已经是一个引用类型就是指针

    切片 切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型) ...

  4. jquery[siblings]取得一个包含匹配的元素集合中每一个元素的所有唯一同辈元素的元素集合

    取得一个包含匹配的元素集合中每一个元素的所有唯一同辈元素的元素集合,用于筛选同辈元素的表达式 $("#pageList").click(function(){ $(this).pa ...

  5. C语言里的指针探析——type *name[] 在函数参数里面,是一个二维指针

    type *name[] 在函数参数里面声明和不在函数里面声明其实不一样. type *name[] 如果在函数参数里声明,则name 是一个二维指针,并不是一个指针数组,而如果不在函数参数里声明,则 ...

  6. [leetcode-117]填充每个节点的下一个右侧节点指针 II

    (1 AC) 填充每个节点的下一个右侧节点指针 I是完美二叉树.这个是任意二叉树 给定一个二叉树 struct Node { int val; Node *left; Node *right; Nod ...

  7. 最短路径(给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。)

    给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 例: 输入: [ [1,3,1], [1,5,1], [ ...

  8. 定义一个包含标签inclusion_tag, 调用模板时报错.. 应该是路径 不对吧...我的templates 是放在app 目录下的.<待处理>

    # 自定义模板标签. 标签的作用,在模板中 实现逻辑,如if ,for 等 from django.template import Library from datetime import datet ...

  9. cocos2d-x getParent() 获得一个父类的一个node型指针,转换为父类类型

    void CenterLayer::zhanzheng(CCObject* pSender){ ((GameScene*)this->getParent())->showLayer(Gam ...

随机推荐

  1. 使用CocoaPods创建自己的私有库-iOS组件化第一步

    目前iOS组件化常用的解决方案是Pod+路由+持续集成,通常架构设计完成后第一步就是将原来工程里的模块按照架构图分解为一个个独立的pod工程(组件),今天我们就来看看如何创建一个Pod私有库. 新建: ...

  2. leadcode的Hot100系列--226. 翻转二叉树

    这玩意儿基本上还是遍历的那一套, 这里使用先序遍历的方式,直接对左右子树进行对调即可. (虽然看题目的时候,感觉都一样,但真正写出来之后,印象还是深刻了很多) struct TreeNode* inv ...

  3. C# 6 新语法

    1. using 声明的静态用法 2. 表达式体方法 3. 表达式体属性 4. 自动实现的属性初始化器 5. 只读的自动属性 6. nameof 运算符 7. 空值传播运算符 8. 字符串插值 9. ...

  4. [AI开发]目标跟踪之计数

    基于视频结构化的应用中,目标在经过跟踪算法后,会得到一个唯一标识和它对应的运动轨迹,利用这两个数据我们可以做一些后续工作:测速(交通类应用场景).计数(交通类应用场景.安防类应用场景)以及行为检测(交 ...

  5. C语言学习书籍推荐《明解C语言》下载

    柴田望洋 (作者), 管杰 (译者), 罗勇 (译者) <明解C语言>是日本的C语言经典教材,自出版以来不断重印.修订,被誉为“C语言圣经”.作者在日本IT界家喻户晓,出版过一系列极富影响 ...

  6. android_layout_linearlayout(一)

    这几天一直在研究线性布局这块,记录下一些研究心得,作为参考之用. 一.金刚钻:(线性布局,英文名 linearLayout) 布局xml文件中统大者是一个线性布局,它的长宽都已经fill_parent ...

  7. C#拼装JSON数组简易方法

    下面是我们想要拼接出来的JSON字符串,返回给前台 {"success":"true","msg":"","d ...

  8. 在rman恢复中incarnation的概念

    摘要 本文主要介绍incarnation的由来,在rman恢复中的作用,以及相关rman恢复的注意事项. 概念说明 从10g开始,incarnation被引入,用于跨越resetlogs进行恢复,由此 ...

  9. ZIP:GZIP

    GZIPInputStream: GZIPInputStream(InputStream in) :使用默认缓冲区大小创建新的输入流. GZIPInputStream(InputStream in, ...

  10. [原创]lvs+ospf+nginx实现高可用大流量web架构

    lvs+ospf+nginx实现高可用大流量web架构配置总概述 架构图: 配置如下: .quagga之zebra配置: # cat /etc/quagga/zebra.conf ! ! Zebra ...