go中的nil容易踩的坑

来源:公众号【peachesTao】
作者:peachesTao
原文地址:go中的nil容易踩的坑

大家好,我是peachesTao,有2个多月没有更新了,没有达到一个月更新两次的标准,过了个年变得懒散了,现在已找回专注,今天给大家介绍一下go中使用nil时容易踩的坑。

先看一段代码

package main

import "fmt"

type MyError struct {
 Code int
 Msg  string
}

func (e *MyError) Error() string {
 return fmt.Sprintf("code:%d, msg:%s", e.Code, e.Msg)
}

var errMap = map[int]*MyError{
 1001: {Code: 1001, Msg: "服务内部错误"},
 1002: {Code: 1001, Msg: "无效参数"},
}

func getErr(code int) error {
 return errMap[code]
}

func main() {
 err := getErr(1003)
 if err != nil {
  fmt.Println(err.Error())
  return
 }
 fmt.Println("not found error")
}

这段代码定义了一个实现了error接口的MyError对象,它有Code和Msg两个属性,有一个全局map[int]*MyError,还有一个通过code获取error的方法getErr,最后main函数调用getErr方法,打印出存在于map中的error格式化字符串。

大家想想看最后输出的结果会是什么?我想知道有多少人跟我之前认为的一样:1003不存在于map中,value是空指针类型为nil,最后输出“not found error”,如果你也这么认为,那么你算来对了,请继续往下看。

其实程序会panic

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a8d13]

goroutine 1 [running]:
main.(*MyError).Error(0x0, 0xc000106180, 0x3eb)
        /Users/taolulu/tao/project/public_number/nil_use/main.go:11 +0x33
main.main()
        /Users/taolulu/tao/project/public_number/nil_use/main.go:26 +0x65
exit status 2

先不急着解释为什么会panic,我们来看一下nil的定义

nil的定义

按照源码(src/builtin/builtin.go)中文档的定义:nil是一个预先声明的标识符,它表示指针、通道、函数、接口、映射或切片类型的零值。

// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type

可以看出,nil不是go的关键字,只是一个变量,我们甚至可以改变它的定义,比如改成:

var nil= errors.New("nil") 

照样可以编译通过,当然不建议改动源码。

下面列出几种类型的nil的定义

var myErr *MyError // nil
var ch chan int // nil
var myFunc func() // nil
var i interface{} // nil
var m map[int]string // nil
var slice []string // nil


nil的使用

我们这里直接看接口类型nil的使用,其他类型比较简单,这里略过。

var iface interface{} // nil
fmt.Println(iface==nil) // true

iface=myErr
fmt.Println(myErr) // nil
fmt.Println(iface==nil) // false

可以看到:iface等于nil,但将myErr赋值给iface后iface就不等于nil了,同时打印出iface的值又是nil,怎么nil !=nil呢?

原因是:接口底层其实由类似于type和data两部分组成,当type和data同时为nil时接口才等于nil,当把myErr赋值给iface时,iface的type为*MyError,data为nil,type和data不同时为nil,所以iface不等于nil。

之所以fmt.Println(iface)结果为nil,是因为Println函数输出的是iface的data部分也就是值的内容。

有了上面的铺垫我们再来分析文章开头那个例子

func getErr(code int) error {
 return errMap[code]
}

err := getErr(1003)

getErr(1003)返回的是一个error类型的接口,type为*MyError,data为nil,由于type和data不同时为nil所以err不等于nil,如果要让err等于nil该怎么办呢?我们只需要在code不存在时直接return nil就行,如下:

func getErr(code int) error {
 if err, ok := errMap[code];ok{
  return err
 }
 return nil
}


总结

go中的接口由type和data两部分组成,只有当type和data同时为nil时接口才等于nil
 

参考链接

[理解Go语言的nil] https://zoyi14.smartapps.cn/pages/note/index?slug=dd80f6be7969&origin=share&_swebfr=1&_swebFromHost=mibrowser

[Go语言基础:使用nil的注意事项] https://zhuanlan.zhihu.com/p/402369194



如果这篇文章对你有帮助,可以关注我的公众号,第一时间获取最新的原创文章

avatar

你可能感兴趣的:(Golang,golang,开发语言,后端)