Go 神坑 1 —— interface{} 与 nil 的比较
文章目录
- 1.前言
- 2.出乎意料的比较结果
- 3.寻找问题所在
- 4.避坑大法
- 5.小结
- 参考文献
1.前言
interface 是 Go 里所提供的非常重要的特性。一个 interface 里可以定义一个或者多个函数,例如系统自带的io.ReadWriter
的定义如下所示:
type ReadWriter interface {Read(b []byte) (n int, err error)Write(b []byte) (n int, err error)
}
任何类型只要它提供了 Read 和 Write 的实现,那么这个类型便实现了这个 interface(duck-type),而不像 Java 需要开发者使用 implements 标明。
然而 Go 的 interface 在使用过程中却有不少的坑,需要特别注意。本文就记录一个nil
与interface{}
比较的问题。
2.出乎意料的比较结果
首先看一个比较 nil 切片的比较问题。
func IsNil(i interface{}) {if i == nil {fmt.Println("i is nil")return}fmt.Println("i isn't nil")
}func main() {var sl []stringif sl == nil {fmt.Println("sl is nil")}IsNil(sl)
}
运行上面代码输出结果是怎样的呢?你可能我和我一样,很自信认为输出是下面这样的:
sl is nil
i is nil
但实际输出的结果是:
sl is nil
i isn't nil
Oh my god,为啥一个nil
切片经过空接口interface{}
一中转,就变成了非 nil。
3.寻找问题所在
想要理解这个问题,首先需要理解 interface{} 变量的本质。
Go 语言中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的空接口 interface{}。
Go 语言使用runtime.iface
表示带方法的接口,使用runtime.eface
表示不带任何方法的空接口interface{}
。
一个 interface{} 类型的变量包含了 2 个指针,一个指针指向值的类型,另外一个指针指向实际的值。在 Go 源码中 runtime 包下,我们可以找到runtime.eface
的定义。
type eface struct { // 16 字节_type *_typedata unsafe.Pointer
}
从空接口的定义可以看到,当一个空接口变量为 nil 时,需要其两个指针均为 0 才行。
回到最初的问题,我们打印下传入函数中的空接口变量值,来看看它两个指针值的情况。
// InterfaceStruct 定义了一个 interface{} 的内部结构
type InterfaceStruct struct {pt uintptr // 到值类型的指针pv uintptr // 到值内容的指针
}// ToInterfaceStruct 将一个 interface{} 转换为 InterfaceStruct
func ToInterfaceStruct(i interface{}) InterfaceStruct {return *(*InterfaceStruct)(unsafe.Pointer(&i))
}func IsNil(i interface{}) {fmt.Printf("i value is %+v\n", ToInterfaceStruct(i))
}func main() {var sl []stringIsNil(sl)IsNil(nil)
}
运行输出:
i value is {pt:6769760 pv:824635080536}
i value is {pt:0 pv:0}
可见,虽然 sl 是 nil 切片,但是其本上是一个类型为 []string,值为空结构体 slice 的一个变量,所以 sl 传给空接口时是一个非 nil 变量。
再细究的话,你可能会问,既然 sl 是一个有类型有值的切片,为什么又是个 nil。
针对具体类型的变量,判断是否是 nil 要根据其值是否为零值。因为 sl 一个切片类型,而切片类型的定义在源码包src/runtime/slice.go
我们可以找到。
type slice struct {array unsafe.Pointerlen intcap int
}
我们继续看一下值为 nil 的切片对应的 slice 是否为零值。
type slice struct {array unsafe.Pointerlen intcap int
}func main() {var sl []stringfmt.Printf("sl value is %+v\n", *(*slice)(unsafe.Pointer(&sl)))
}
运行输出:
sl value is {array:<nil> len:0 cap:0}
不出所料,果然是零值。
至此解释了开篇出乎意料的比较结果背后的原因:空切片为 nil 因为其值为零值,类型为 []string 的空切片传给空接口后,因为空接口的值并不是零值,所以接口变量不是 nil。
4.避坑大法
知道前面有个坑,如何避免不掉进去。我们想到的做法无非就两种:一填坑,二绕过。
因为这是由 Go 的特性决定,直白点就是设计如此。面对此坑,我们开发者只能望洋兴叹,无可奈何,留给 Go 核心团队那帮天才来填坑吧。
我们能做的就是绕过此坑。如何绕过呢,有两个办法:
(1)既然值为 nil 的具型变量赋值给空接口会出现如此莫名其妙的情况,我们不要这么做,再赋值前先做判空处理,不为 nil 才赋给空接口;
(2)使用reflect.ValueOf().IsNil()
来判断。不推荐这种做法,因为当空接口对应的具型是值类型,会 panic。
func IsNil(i interface{}) {if i != nil {if reflect.ValueOf(i).IsNil() {fmt.Println("i is nil")return}fmt.Println("i isn't nil")}fmt.Println("i is nil")
}func main() {var sl []stringIsNil(sl) // i is nilIsNil(nil) // i is nil
}
5.小结
Go 简单高效,功能强大,如此优秀的语言也存在很多让人误用的特性,本文便记录万坑之一的 nil 赋给空接口时判空失效的问题,后面会继续给出 Go 使用的常见问题,帮助大家少踩坑。
切记,Go 中变量是否为 nil 要看变量的值是否是零值。
切记,不要将值为 nil 的变量赋给空接口。
参考文献
Golang 接口相等比较注意要点
Go语言第一深坑 - interface 与 nil 的比较
Go 语言设计与实现.4.2接口
Golang 并发赋值的安全性探讨
Go 神坑 1 —— interface{} 与 nil 的比较相关推荐
- panic: interface conversion: interface {} is nil, not chan *sarama.ProducerError
使用golang kafka sarama 包时,遇到如下问题: 高并发情况下使用同步sync producer,偶尔遇到crash: panic: interface conversion: int ...
- Go语言第一深坑 - interface 与 nil 的比较 (转)
http://studygolang.com/articles/10635 转载于:https://www.cnblogs.com/majianguo/p/7426320.html
- [go]空nil与interface中的空指针
文章目录 nil slice map interface 指针是否为空 Go语言中任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针.函数.i ...
- golang 面试题(十三)interface内部结构和nil详解
1.以下代码打印出来什么内容,说出为什么. package mainimport ("fmt" )type People interface {Show() }type Stude ...
- c++ 判断nil_golang A=nil,B=A,but B!=nil 这是真的
一.现象 在某次开发过程中,想把参数验证放在middleware中封装达到提升开发效率的目的,结果出现了如下图所示的现象,验证返回的err明明是nil,但是用if判断的时候err却不是nil (图1 ...
- golang 接口类型 interface 简介
目录 1. Go 语言与鸭子类型的关系 2. 值接收者和指针接收者的区别 方法 值接收者和指针接收者 两者分别在何时使用 3. iface 和 eface 的区别是什么 4. 接口的动态类型和动态值 ...
- golang 反射_golang原理篇- nil:接口类型和值类型的区别
interface接口类型是golang的最重要的数据结构,底层是value和type组成,实现interface的struct的实例都能赋值给接口类型的变量,实现动态value的能力.type记录v ...
- 理解Go Interface
理解Go Interface 1 概述 Go语言中的接口很特别,而且提供了难以置信的一系列灵活性和抽象性.接口是一个自定义类型,它是一组方法的集合,要有方法为接口类型就被认为是该接口.从定义上来看,接 ...
- golang 接口类型 interface 简介使用
1. Go 语言与鸭子类型的关系 先直接来看维基百科里的定义: If it looks like a duck, swims like a duck, and quacks like a duck, ...
最新文章
- iec61850采样协议(9-1、9-2)解析(二)
- uniapp光标自动定义到文本框_如何在Mac上的照片应用中创建自定义日历
- Java JNI简单实现
- Java各进制之间的转换
- HTML+CSS+JS实现 ❤️canvas 3D立体图片相册幻灯片❤️
- GradView使用举例
- python爬虫代码示例 动态_python动态爬虫的实例分享
- jetson tx2上运行mobilenet-ssd的坑:interrupted by signal 9: SIGKILL
- 通用即插即用监视器驱动下载_驱动之家和驱动精灵哪个好
- 基于Android的海康威视的二次开发
- 动态电路电容电感充放电分析
- 支持向量机(股票)——Python量化
- android 图片释放内存吗,手机内存不足?掌握这几招让手机瞬间释放几个G!
- HDU-4417-Super Mario(划分树+二分)
- centos8重启网卡命令nmcli
- c语言双字节异或,C语言 按位异或实现加法
- VC#复习资料(是轩~)
- arch linux添加用户,Arch Linux配置教程
- 微信小程序制作科学计算器(控制台接受显示数据)
- Windows 使用技巧