Go中nil指针解引用会panic而非空指针异常;需区分指针、接口、map等nil语义:指针判nil用==nil,接口需类型断言后判底层值,map读安全写需初始化,slice可直接append,channel为nil时阻塞,func调用前必须判空。
Go 语言中没有“空指针异常”这个概念,nil 是合法值,但对 nil 指针解引用会触发 panic —— 这是运行时错误,不是编译错误。
Go 中指针变量本身可以是 nil,比如 *int、*string 或自定义结构体指针。判断方式统一用 == nil:
var p *int
if p == nil {
fmt.Println("p is nil")
}
但要注意:方法接收者如果是值类型,即使传入 nil 指针,调用也不会 panic(因为 Go 会自动解引用并复制值);而指针接收者方法在 nil 上调用时,只要不访问字段或解引用内部指针,也是安全的。常见陷阱是:
func (s *MyStruct) Do() { s.field = 1 } —— 若 s 为 nil,运行时报错 invalid memory address or nil pointer dereference
func (s *MyStruct) IsEmpty() bool { return s == nil || s.field == "" } —— 安全,因为先判 nil
这是最常被误解的一点。一个接口变量(如 io.Reader)可以是 nil,但它内部的动态值也可能是一个非 nil 指针。反过来,一个非 nil 接口变量,其底层指针可能是 nil:
var r io.Reader = (*bytes.Buffer)(nil) fmt.Println(r == nil) // false —— 接口不为 nil fmt.Println(r.Read(nil)) // panic: nil pointer dereference
所以不能只靠 r == nil 来防护,必须确保接口底层值可安全使用。典型做法是显式类型断言后判空:
if buf, ok := r.(*bytes.Buffer); ok && buf != nil {
// safe to use buf
}
nil 在不同内置类型中语义不同,但都和指针无关(它们本身不是指针类型,只是底层可能含指针)。关键区别:
map:nil map 可安全读(返回零值),但写会 panic —— 必须用 make 初始化slice:nil slice 长度为 0,可安全读写(append 自动分配),无需预判 nil
channel:nil channel 会永远阻塞 —— 常用于关闭控制流,但不能直接 send/receivefunc:nil 函数调用直接 panic,务必判空再调用interface{}:同上文,需区分接口值本身与底层值与其反复检查 nil,不如从设计上减少裸指针暴露。推荐做法:

nil 防护,而不是要求调用方保证非空(例如 json.Unmarshal 接受 nil 指针会 panic,但标准库通常已处理)errors.Is(err, os.ErrNotExist) 替代手动比对 err == nil,尤其在 error 处理链中nil 输入,验证边界行为 —— Go 的单元测试很容易覆盖这类 case真正麻烦的从来不是怎么写 if p != nil,而是忘记接口变量背后藏着一个 nil 指针,还把它传给了别人的方法。