Golang中Nil检查失败的逻辑是什么?
Golang中Nil检查失败的逻辑是什么? 大家好, 我接触Go语言的时间相对较短(但我已经有超过15年的开发经验),我写了类似下面这样的代码,它让我困惑了好几个小时。
var _ error = &Error{}
type Error struct{ message string }
func (e *Error) Error() string { return e.message }
func call1() error { return call2() }
func call2() *Error { return nil }
func Test_Gotcha(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Failed with a panic for null usage")
}
}()
err := call1()
if err != nil {
anError, ok := err.(*Error)
if ok {
fmt.Printf("this panics on nil reference %s\n", anError.message)
}
}
}
我想知道,err != nil 为什么没能捕获到从 call2 返回的 <*Error> nil 背后的逻辑是什么?
大多数时候Go语言看起来相当简单和直接,但这似乎是一个在非常糟糕的地方遇到的陷阱?
附注:我使用的是Go 1.13.1。
更多关于Golang中Nil检查失败的逻辑是什么?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的……当 anError.message 对值为 nil 的类型进行 panic 时,描述中的代码重现了该情况。
更多关于Golang中Nil检查失败的逻辑是什么?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
是的,谢谢 :) 但这仍然没有解释其背后的逻辑……它为什么要这样做?以及为什么这样设计?
你好,
你能扩展一下以测试结构体,或者能在 Go Playground 上分享一个可运行的示例吗?
这个问题在 Go 官方网站的“常见问题解答”中已有说明。请参阅 https://golang.org/doc/faq#nil_error
其背后的逻辑之一是,Go语言中的nil指针并不总是像其他语言中那样“糟糕”。例如,在nil指针上调用方法是允许的,并且这些方法可以执行有用的操作。这意味着,一个接口的T字段为*foo类型而V字段为nil,可能是有用且符合设计意图的。因此,严格来说,nil接口是指那种既没有类型(也没有值)的接口。
从另一个角度来看,如果一个接口是nil却包含信息(类型信息),这会是件奇怪的事情。按照目前的定义,nil严格地表示零值,这既简单又易于理解——尽管它导致了关于错误处理的这个恼人的注意事项。
这是一个典型的Go语言接口值nil检查陷阱。问题的核心在于接口值是否为nil取决于其动态类型和动态值两部分。
在你的代码中,call2()返回的是*Error类型的nil值,但call1()的返回类型是error接口。当具体的nil指针被赋值给接口时,接口值本身并不是nil。
package main
import (
"fmt"
)
var _ error = &Error{}
type Error struct{ message string }
func (e *Error) Error() string {
if e == nil {
return "<nil>"
}
return e.message
}
func call1() error { return call2() }
func call2() *Error { return nil }
func main() {
err := call1()
// 这里err != nil返回true,因为接口值不是nil
fmt.Printf("err == nil: %v\n", err == nil) // false
fmt.Printf("err value: %v\n", err) // <nil>
fmt.Printf("Type: %T\n", err) // *main.Error
// 类型断言成功,但anError是nil指针
if anError, ok := err.(*Error); ok {
fmt.Printf("anError == nil: %v\n", anError == nil) // true
// 这里访问anError.message会导致panic
// fmt.Printf("%s\n", anError.message)
}
}
要正确处理这种情况,可以在类型断言后检查具体的指针是否为nil:
err := call1()
if err != nil {
if anError, ok := err.(*Error); ok && anError != nil {
fmt.Printf("Safe access: %s\n", anError.message)
}
}
或者在Error()方法中添加nil检查:
func (e *Error) Error() string {
if e == nil {
return "<nil Error>"
}
return e.message
}
这种设计的原因是:接口值包含两个部分——类型信息和值信息。当*Error类型的nil值被赋值给error接口时,接口的类型信息是*Error,值信息是nil,因此接口值本身不是nil。

