零值是什么?nil 有哪些常见陷阱(nil slice、nil map)?
结论先行
零值是变量声明后未显式初始化时自动获得的值:数值为 0、bool 为 false、string 为 "",指针与 slice/map/chan/func/interface 为 nil,struct 的零值是各字段零值的递归组合。零值让“先声明后使用”变得安全,但 nil 不等于“空”,nil slice、nil map、nil interface 各有各的坑。
要点
- 零值速查:数字 0 / false / "" / nil(指针、slice、map、chan、func、interface);struct 递归取零值。
- 零值可用是设计原则:sync.Mutex、bytes.Buffer 等类型零值即可直接用,无需构造函数。
- nil slice 与空 slice:两者 len/cap 都是 0,但 nil slice 没有底层数组;“是否为空”一律用 len(s)==0 判断,不要用 s==nil。
- 序列化差异:nil slice 常被编码为 null,空 slice 编码为 [];想稳定输出 [] 就先初始化再使用。
- nil map 只能读(返回元素零值),写入会 panic:assignment to entry in nil map;map 使用前必须 make。
- nil 指针装进 interface 后接口不等于 nil:var p *T; var i any = p 之后 i != nil 为 true,这是经典的 typed nil 陷阱。
- nil channel 收发都会永久阻塞,但在 select 里永不就绪,反而可用来临时禁用某一分支。
示例
var s []int // nil slice
var e = []int{} // 空 slice,非 nil
fmt.Println(s == nil, len(s) == 0) // true true
fmt.Println(e == nil, len(e) == 0) // false true
var m map[string]int // nil map
_ = m["k"] // 读:返回零值 0,安全
// m["k"] = 1 // 写:panic
var p *int
var i any = p // 把 nil 指针装进空接口
fmt.Println(i == nil) // false:动态类型是 *int
常见追问/记忆点
- 追问:如何判断 interface 里装的到底是不是 nil 指针?答:用 reflect.ValueOf(i) 检查 Kind 与 IsNil,或约定接口内只放值不放指针。
- 追问:返回 error 时为什么经常踩“非 nil”的坑?答:把 (*MyErr)(nil) 赋给 error 接口后 err != nil,应直接返回 nil。
- 追问:判空为什么推荐 len(s)==0?答:它同时覆盖 nil slice 与空 slice,语义是“没有元素”而非“没有底层数组”。
- 记忆:判空用 len,判“有没有底层数据”才用 == nil;interface 判 nil 要当心动态类型。