在 Go 里,接口值可以理解为一对信息:动态类型和动态值。只有这两部分都为空时,接口才真正等于 nil。
下面的代码经常让人意外:
package main
import "fmt"
type User struct {
Name string
}
func findUser() *User {
return nil
}
func main() {
var result any = findUser()
fmt.Println(result == nil) // false
}
findUser() 返回的是一个类型为 *User 的空指针。它赋值给 any 后,接口的动态类型是 *User,动态值才是 nil。因为动态类型仍然存在,所以整个接口不等于 nil。
这个问题在返回 error 时尤其危险:
type QueryError struct{}
func (*QueryError) Error() string { return "query failed" }
func query() error {
var err *QueryError
return err // 返回的 error 不等于 nil
}
更稳妥的做法是,在没有错误时直接返回无类型的 nil:
func query() error {
// ...
return nil
}
如果函数必须返回具体指针类型,就在把它装进接口之前判断。排查此类问题时,可以用 %T 和 %v 同时打印类型和值:
fmt.Printf("type=%T value=%v\n", result, result)
记忆方式很简单:接口不是单纯的“盒子里的值”,它还携带盒子的类型标签。标签没有消失,接口就不算真正为空。