现象在日常开发中,可能一不小心就会掉进 Go
语言的某些陷阱里,而本文要介绍的 nil ≠ nil
问题,便是其中一个,初看起来会让人觉得很诡异,摸不着头脑 。
先来看个例子:
type CustomizedError struct { ErrorCode int Msgstring}func (e *CustomizedError) Error() string { return fmt.Sprintf("err code: %d, msg: %s", e.ErrorCode, e.Msg)}
func main() { txn, err := startTx() if err != nil {log.Fatalf("err starting tx: %v", err) } if err = txn.doUpdate(); err != nil {log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}type tx struct{}func startTx() (*tx, error) { return &tx{}, nil}func (*tx) doUpdate() *CustomizedError { return nil}func (*tx) commit() error { return nil}
这是一个简化过了的例子,在上述代码中,我们创建了一个事务,然后做了一些更新,在更新过程中如果发生了错误,希望返回对应的错误码和提示信息 。
如果感兴趣的话,可以在这个地址在线运行这份代码:
Go Playground - The Go Programming Language
看起来每个方法都会返回 nil
,应该能顺利走到最后一行,输出 success
才对 , 但实际上,输出的却是:
err updating: <nil>
寻找原因为什么明明返回的是 nil
,却被判定为 err ≠ nil
呢?难道这个 nil
也有什么奇妙之处?
这就需要我们来更深入一点了解 error
本身了 。在 Go 语言中,error
是一个 interface
, 内部含有一个 Error()
函数 , 返回一个字符串,接口的描述如下:
// The error built-in interface type is the conventional interface for// representing an error condition, with the nil value representing no error.type error interface { Error() string}
而对于一个变量来说 , 它有两个要素 , 一个是 type T
,一个是 value V
, 如下图所示:
文章插图
来看一个简单的例子:
var it interface{}fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // <nil> <invalid reflect.Value>it = 1fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // int 1it = "hello"fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // string hellovar s *stringit = sfmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string <nil>ss := "hello"it = &ssfmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) // *string 0xc000096560
在给一个 interface
变量赋值前,T
和 V
都是 nil
, 但给它赋值后 , 不仅会改变它的值,还会改变它的类型 。当把一个值为
nil
的字符串指针赋值给它后 , 虽然它的值是 V=nil
, 但它的类型 T
却变成了 *string
。此时如果拿它来跟
nil
比较,结果就会是不相等,因为只有当这个 interface
变量的类型和值都未被设置时,它才真正等于 nil
。再来看看之前的例子中,
err
变量的 T
和 V
是如何变化的:func main() { txn, err := startTx() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) if err != nil {log.Fatalf("err starting tx: %v", err) } if err = txn.doUpdate(); err != nil {fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err))log.Fatalf("err updating: %v", err) } if err = txn.commit(); err != nil {log.Fatalf("err committing: %v", err) } fmt.Println("success!")}
输出如下:<nil> <invalid reflect.Value>*err.CustomizedError <nil>
在一开始,我们给 err
初始化赋值时,startTx
函数返回的是一个 error
接口类型的 nil
。此时查看其类型 T
和值 V
时,都会是 nil
。txn, err := startTx()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // <nil> <invalid reflect.Value>func startTx() (*tx, error) { return &tx{}, nil}
而在调用 doUpdate
时,会将一个 *CustomizedError
类型的 nil
值赋值给了它,它的类型 T 便成了 *CustomizedError
,V 是 nil
。err = txn.doUpdate()fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) // *err.CustomizedError <nil>
所以在做 err ≠ nil
的比较时,err
的类型 T
已经不是 nil
,前面已经说过,只有当一个接口变量的
推荐阅读
- 黑鲨4pro为什么不建议买_黑鲨4pro骂声一片原因
- 电脑?号怎么打出来(电脑为什么打不出来字)
- 羊了个羊为什么进不去
- 羊了个羊通关率为什么会不到0.1%
- McAfee如何卸载干净(mcafee为什么卸载不了)
- 语音发出后录音还在继续_语音发出之后为什么还在录音
- 不吹不黑,锤子手机真的好用吗为什么(现在的锤子手机怎么样)
- 红心大战到底怎么玩儿?为什么我总是多分(红心大战得分越低越好吗)
- golang中的nil接收器
- 为什么说:保险柜没有钥匙时,只用笔帽就可以打开怎么做到的