当 nil 不等于 nil?深度剖析 Go 的 typed nil 大坑
0. 一个能“悄悄坑你”的真实示例:SError 的故事 先看你提到的这段代码,表面上看非常正常,甚至很多人第一眼不会觉得哪里有问题: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package main import ( "log" "sync" ) type SError struct { cause error // Wrapped error which is the root cause. text string // Error text, which is created by New* functions. i18nText string // 本地错误文字,用于客户端显式中文 ignored bool // 能否忽略该错误 info map[string]string infoMutex sync.RWMutex } func (e *SError) Error() string { return e.text } // demoRetSerr 返回 (int32, *SError)。看上去,如果不想返回任何错误,就直接返回 (0, nil) func demoRetSerr() (int32, *SError) { return 0, nil } func main() { var err error // 这里把第二个返回值的 *SError 赋给了 interface{} 类型的 err _, err = demoRetSerr() if err != nil { log.Printf("err != nil. err: %v", err) } } 0.1 乍看之下哪里会有问题? 我们 demoRetSerr 函数直接返回 (0, nil),而函数签名的第二个参数类型是 *SError。 回到 main 函数中,err 是一个 error 接口,承接了那个 nil 指针。 如果“typed nil” 现象出现,那么 err != nil 这个判断就会莫名其妙地通过,从而在日志里打印出“err != nil. err: <nil>”。有时更可怕的是,实际逻辑会被误判,可能执行本不该执行的错误处理分支。 在某些 Go 版本或特定编译器优化下,你可能发现控制台就输出 err != nil. err: <nil>,让人“一头雾水”。这就是一个十分典型、却很隐蔽的 typed nil 场景。即使你写 return nil,对编译器来说: ...