interface{} == nil 仅当类型指针_type和数据指针data同时为零值时才成立;若_type非零(如*int(nil)赋值后),即使data为nil,接口也不等于nil。

interface{} == nil 的判断逻辑不是“值为空”
Go 中 interface{} 变量是否等于 nil,不看它存的值是不是空,而要看它的两个底层字段是否**同时为零**:一个是类型指针 _type,一个是数据指针 data。只有二者都是 0(即全 16 字节为 0),interface{} 才真正是 nil。
常见错误现象:
- 把一个
*int类型的nil指针赋给interface{},结果该接口变量!= nil - 函数返回
error类型但内部返回了(*MyError)(nil),调用方写if err != nil却没进分支,导致 panic
原因很简单:赋值时,_type 字段被填成了 *MyError 的类型信息地址(非零),而 data 是 nil —— 一真一假,整体不为 nil。
eface 结构体暴露了“nil 不等于 nil”的真相
interface{} 对应运行时结构 eface,定义在 runtime/iface.go 中:
type eface struct {
_type *_type
data unsafe.Pointer
}
汇编层面,== nil 判断就是对这 16 字节做一次内存比较(x86-64 下是两条 MOVQ + 一次 ORQ 或直接 TESTQ)。只要 _type 非零,哪怕 data 是 nil,整个结构体就不等于 nil。
典型场景:
-
var p *int = nil; var i interface{} = p→i._type != nil,所以i != nil -
var i interface{} = nil→i._type == nil && i.data == nil,所以i == nil
如何安全判断 interface{} 是否“逻辑上为空”
不能只依赖 == nil,尤其当变量来自函数返回或类型断言后。推荐组合使用反射和类型检查:
- 对任意
interface{},用reflect.ValueOf(x).Kind() == reflect.Ptr && reflect.ValueOf(x).IsNil()判断底层指针是否为空 - 若已知具体类型(如
error),优先用类型断言后判空:if e, ok := err.(someError); ok && e == nil - 更稳妥的做法是避免让
nil指针流入接口:函数返回error时,直接返回nil,而非(*MyError)(nil)
示例:
func isNil(v interface{}) bool {
if v == nil {
return true
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice:
return rv.IsNil()
}
return false
}
error 类型为什么通常不踩这个坑
标准库中 error 是接口类型,但绝大多数实现(包括 errors.New、fmt.Errorf)返回的是**值类型实例的指针**,且函数签名明确返回 error 接口。当你写 var err error = nil,编译器生成的是纯 eface{nil, nil};而如果某函数内部返回 (*MyErr)(nil),那它就破坏了这一约定。
关键点在于:接口变量的“nil 性”完全取决于它**怎么被赋值**,而不是它“看起来像不像空”。同一个 *T 类型,直接赋给 interface{} 和先转成 T 再赋,底层 _type 字段可能指向不同运行时类型元数据。
最易被忽略的是跨包传递:别人导出的函数若返回带类型的 nil 接口值,你无法从签名看出它是否安全 —— 必须查源码或文档,或统一用反射兜底。


















