在 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)

记忆方式很简单:接口不是单纯的“盒子里的值”,它还携带盒子的类型标签。标签没有消失,接口就不算真正为空。