来源:公众号【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的定义
按照源码(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的使用,其他类型比较简单,这里略过。
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