go interface{}==nil 几种坑及原理
本文是Go比较有名的一个坑,在以前面试的时候也被问过,为什么想起来写这个?
因为我们线上就真实出现过这个坑,写给不了解的人在使用 if err != nil 的时候提高警惕。
Go语言的interface{}在使用过程中有一个特别坑的特性,当你比较一个interface{}类型的值是否是nil的时候,这是需要特别注意避免的问题。
先来看看一个demo:
package main
import "fmt"
type ErrorImpl struct{}
func (e *ErrorImpl) Error() string {
return ""
}
var ei *ErrorImpl
var e error
func ErrorImplFun() error {
return ei
}
func main() {
f := ErrorImplFun()
fmt.Println(f == nil)
}
//输出:
false
为什么不是true?
想要理解这个问题,首先需要理解interface{}变量的本质。在Go语言中,一个interface{}类型的变量包含了2个指针,一个指针指向值的在编译时确定的类型,另外一个指针指向实际的值。
// InterfaceStructure 定义了一个interface{}的内部结构
type InterfaceStructure struct {
pt uintptr // 到值类型的指针
pv uintptr // 到值内容的指针
}
// asInterfaceStructure 将一个interface{}转换为InterfaceStructure
func asInterfaceStructure(i interface{}) InterfaceStructure {
return *(*InterfaceStructure)(unsafe.Pointer(&i))
}
func main() {
var i1, i2 interface{}
var v1 int = 23
var v2 int = 23
i1 = v1
i2 = v2
fmt.Printf("sizeof interface{} = %d\n", unsafe.Sizeof(i1))
fmt.Printf("i1 %v % v\n", i1, asInterfaceStructure(i1))
fmt.Printf("i2 %v % v\n", i2, asInterfaceStructure(i2))
var nilInterface interface{}
var str *string
fmt.Printf("nil interface = % v\n", asInterfaceStructure(nilInterface))
fmt.Printf("nil string = % v\n", asInterfaceStructure(str))
fmt.Printf("nil = % v\n", asInterfaceStructure(nil))
}
//输出:
sizeof interface{} = 16
i1 23 {pt:4812032 pv:825741246928}
i2 23 {pt:4812032 pv:825741246936}
nil interface = {pt:0 pv:0}
nil string = {pt:4802400 pv:0}
nil = {pt:0 pv:0}
当我们将一个具体类型的值赋值给一个interface{}类型的变量的时候,就同时把类型和值都赋值给了interface{}里的两个指针。如果这个具体类型的值是nil的话,interface{}变量依然会存储对应的类型指针和值指针。
如何解决?
方法一: 返回的结果进行非nil检查,然后再赋值给interface{}变量
type ErrorImpl struct{}
func (e *ErrorImpl) Error() string {
return ""
}
var ei *ErrorImpl
var e error
func ErrorImplFun() error {
if ei == nil {
return nil
}
return ei
}
func main() {
f := ErrorImplFun()
fmt.Println(f == nil)
}
//输出:
true
方法二:返回具体实现的类型而不是interface{}
package main
import "fmt"
type ErrorImpl struct{}
func (e *ErrorImpl) Error() string {
return ""
}
var ei *ErrorImpl
var e error
func ErrorImplFun() *ErrorImpl {
return ei
}
func main() {
f := ErrorImplFun()
fmt.Println(f == nil)
}
//输出:
true
解决由于第三方包带来的坑
由于有的error是第三方包返回的,又自己不想改第三方包,只好接收处理的时候想办法。
方法一: 利用interface{}原理
is:=*(*InterfaceStructure)(unsafe.Pointer(&i))
if is.pt==0 && is.pv==0 {
//is nil do something
}
将底层指向值和指向值的类型的指针打印出来如果都是0,表示是nil
方法二:利用断言,断言出来具体类型再判断非空
type ErrorImpl struct{}
func (e ErrorImpl) Error() string {
return "demo"
}
var ei *ErrorImpl
var e error
func ErrorImplFun() error {
//ei = &ErrorImpl{}
return ei
}
func main() {
f := ErrorImplFun()
//当然error实现类型较多的话使用
//switch case方式断言更清晰
res, ok := f.(*ErrorImpl)
fmt.Printf("ok:%v,f:%v,res:%v",
ok, f == nil, res == nil)
}
//输出:
ok:true,f:false,res:true
方法三:利用反射
type ErrorImpl struct{}
func (e ErrorImpl) Error() string {
return "demo"
}
var ei *ErrorImpl
var e error
func ErrorImplFun() error {
//ei = &ErrorImpl{}
return ei
}
func main() {
f := ErrorImplFun()
rv := reflect.ValueOf(f)
fmt.Printf("%v", rv.IsNil())
}
//输出:
true
注意⚠:
断言和反射性能不是特别好,如果不得已再使用,控制使用有助于提升程序性能。
由于函数接收类型导致的panic:
type ErrorImpl struct{}
func (e ErrorImpl) Error() string {
return "demo"
}
var ei *ErrorImpl
var e error
func ErrorImplFun() error {
return ei
}
func main() {
f := ErrorImplFun()
fmt.Printf(f.Error())
}
//输出:
panic: value method main.ErrorImpl.Error called using nil *ErrorImpl pointer
解决:
func (e *ErrorImpl) Error() string {
return "demo"
}
//输出:
demo
可以发现将接收类型变成指针类型就可以了。
以上就是 nil 相关的坑,希望大家可以牢记,如果 ”幸运“ 的遇到了,可以想到这些可能性。
到此这篇关于“go interface{}==nil 几种坑及原理”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!您可能感兴趣的文章:
go interface{}==nil 几种坑及原理
golang 数据库连接池database/sql 实现原理分析
图解 Go 反射实现原理
Go 开发关键技术指南 | 为什么你要选择 Go?(内含超全知识大图)
go golang 笔试题 面试题 笔试 面试
golang积累-接口指针与结构体指针
golang 初始化并赋值_Golang | 既是接口又是类型,interface是什么神仙用法?
go 函数末尾缺少返回值_王垠:Go语言野心勃勃,实际情况又如何
golang 面试题(十三)interface内部结构和nil详解
Golang 微服务教程(一)