
在 go 中,一个实现了 `error` 接口的 nil 指针(如 `(*myerr)(nil)`)传入 `error` 类型参数后,其接口值不为 `nil`,因为接口内部同时存储类型信息和值,`(*myerr, nil)` ≠ `(nil, nil)`。
Go 的接口类型是运行时的二元结构:每个接口值由 动态类型(dynamic type) 和 动态值(dynamic value) 组成。只有当二者均为 nil 时,该接口值才被视为 nil。例如:
- var err error → 底层为 (nil, nil) → err == nil 为 true;
- var g *Goof → g 是 nil 指针,但将其赋给 error 接口时,Go 会自动装箱为 (*Goof, nil) → 类型非空、值为空 → err == nil 为 false。
这正是以下代码输出 "Error is not nil" 的根本原因:
type Goof struct{}
func (g *Goof) Error() string { return "I'm a goof" }
func TestError(err error) {
if err == nil {
fmt.Println("Error is nil")
} else {
fmt.Println("Error is not nil") // 实际执行此分支
}
}
func main() {
var g *Goof // g == nil
TestError(g) // g 被隐式转换为 error 接口,底层为 (*Goof, nil)
}✅ 正确做法:直接使用 error 类型声明或返回 nil
func main() {
var err error // 零值即 (nil, nil)
TestError(err) // 输出 "Error is nil"
// 或在函数中显式返回 nil error
doSomething := func() error {
return nil // 编译器确保返回的是 (nil, nil) 的 error 接口
}
TestError(doSomething()) // 同样输出 "Error is nil"
}⚠️ 注意事项:
- 不要通过 (*SomeStruct)(nil) 构造 error 值再传递,应统一用 error 类型操作;
- 接口比较 == 是严格类型+值匹配,不进行类型兼容性检查(如 int 和自定义 type MyInt int 即使值相同,装入 interface{} 后也不相等);
- 判断 error 是否为空,始终使用 if err != nil —— 这是 Go 社区约定和标准库实践,依赖的就是接口的 (nil, nil) 语义。
总结:Go 的 nil 是类型感知的。理解接口的 (type, value) 内部表示,是避免此类“假 nil”陷阱的关键。始终优先使用 error 类型变量或 return nil,而非手动构造满足接口的 nil 指针。










