Go 函數(shù)返回nil遇到問題避坑分析
前言
go語言寫函數(shù)時經(jīng)常返回nil,然后在函數(shù)外面判斷返回值是否為空。這里有個bug,記錄一下
問題1
(*Type)(nil) ≠ nil
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
上面函數(shù)returnsError返回的 p 永遠不會與 nil 相等。
這是為什么呢,因為 error 是一個 interface,interface 之間比較需要保證兩者的 Type 和 Value 兩兩相等
語言內的 nil 可以理解為一個 Type 和 Value 均為空的 interface 代碼里面返回的 p 雖然 Value 為空,但是 Type 是 *MyError 所以 p!=nil 。
正確寫法
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
這個問題不僅僅是拋出錯誤的時候會出現(xiàn),任何返回 interface 的場景都需要注意。
問題2
type CustomError struct {
Metadata map[string]string
Message string
}
func (c CustomError) Error() string {
return c.Message
}
var (
ErrorA = CustomError{Message:"A", Matadata: map[string]string{"Reason":""}}
ErrorB = CustomError{Message:"B"}
)
func DoSomething() error {
return ErrorA
}
而我們在外部接收到錯誤之后常常會使用 errors.Is 來判斷錯誤類型:
err:=DoSomething()
if errors.Is(err, ErrorA) {
// handle err
}
但是會發(fā)現(xiàn)上面這個判斷無論如何都是 false。研究一下 errors.Is 的源碼:
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflect.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if err = errors.Unwrap(err); err == nil {
return false
}
}
}
可以看到這是一個在 error tree 上遞歸的流程,真值的終結條件是 err==target ,但是前提是 target 本身得是 comparable 的
所以如果我們把一個 map 放入了 error struct,就導致這個 error 變?yōu)?incomparable,永遠無法成功比較。
解決方案也很簡單,就是將 Error 定義指針類型:
var (
ErrorA = &CustomError{Message:"A", Matadata: map[string]string{"Reason":""}}
ErrorB = &CustomError{Message:"B"}
)
指針類型比較只需要是否檢查是否指向同一個對象,這樣就能順利比較了。
參考
以上就是Go 函數(shù)返回nil遇到問題避坑分析的詳細內容,更多關于Go 函數(shù)返回nil避坑的資料請關注腳本之家其它相關文章!

