Jquery中文网 www.jquerycn.cn
Jquery中文网 >  后端编程  >  Go语言  >  正文 go interface{}==nil 几种坑及原理

go interface{}==nil 几种坑及原理

发布时间:2021-05-25   编辑:www.jquerycn.cn
jquery中文网为您提供go interface{}==nil 几种坑及原理等资源,欢迎您收藏本站,我们将为您提供最新的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 微服务教程(一)

[关闭]