必须显式检查 nil 的情况包括:解引用指针、使用未校验的函数返回指针、访问 map 不存在键对应的 nil 值、接口底层值为 nil 时调用方法、指针接收者方法内访问字段;切片、map、channel 零值安全,无需 nil 检查。

什么时候必须显式检查 nil?
Go 不会自动对指针做空值防护,只要解引用 nil 指针(比如访问 ptr.Field 或调用 ptr.Method()),运行时立刻 panic:panic: runtime error: invalid memory address or nil pointer dereference。这不是编译错误,所以容易漏掉。
常见触发场景:函数返回指针但未校验、结构体字段是可选指针、map 查不到键返回 nil 后直接使用、接口变量底层值为 nil 但误以为它“非空”。
- 接口变量
var i io.Reader可能为nil,但i != nil判断的是接口本身是否为空,不是底层值;若底层是*bytes.Buffer且为nil,i.Read()仍 panic - 方法接收者为指针类型时,
nil接收者也能调用——但仅限该方法内不访问任何字段或嵌入字段;一旦碰p.field就崩 - 切片、map、channel 是引用类型,但它们的零值不是
nil指针,而是可用的空值(如len(s) == 0),不需要nil检查;只有*T、func()、interface{}、map[K]V(注意:map 变量本身可为nil)等才真要防nil
if p == nil 之后还能不能直接用 p?
能,但只在当前作用域安全。Go 没有“非空断言”语法,if p != nil 块内可以放心解引用 p,前提是别把它传给可能保存或并发使用的其他函数。
- 不要写
if p != nil { return p.field }然后外面还用p.field—— 编译器不会帮你延续这个假设 - 避免在检查后把
p赋给另一个变量再解引用,除非你确认新变量没逃逸或被闭包捕获 - 如果多个地方都要用
p.field,优先考虑提前返回:if p == nil { return errNilPointer },而不是层层嵌套if
哪些情况看似安全实则危险?
最容易掉坑的是“接口 + 指针”的组合,以及依赖文档却没验证底层实现。
立即学习“go语言免费学习笔记(深入)”;
-
json.Unmarshal如果传入*T且T是 struct,T字段里有指针字段,反序列化失败时这些指针仍是nil,后续业务逻辑直接访问就 panic - 使用
database/sql的Scan时,如果目标是*string但数据库值为 NULL,Scan会设它为nil,不报错也不预警 - 第三方库返回
io.ReadCloser,你以为它一定非空,结果某些 mock 实现或错误路径下返回了nil接口,rc.Read()直接挂 - struct 字段带默认 tag 如
json:",omitempty",反序列化时字段被跳过,指针字段保持nil,但业务代码假定它已被初始化
怎么让 nil 检查更可靠?
靠人肉检查不可持续,得靠工具链和约定来兜底。
- 启用
-vet和staticcheck,它们能发现部分明显未检查的指针解引用(比如函数参数声明为*T却在入口就访问arg.Field) - 在关键函数入口加
if arg == nil { panic("arg is nil") }—— 显式失败比静默 panic 更好定位 - 对可为空的字段,用自定义类型封装并提供安全访问方法:
func (p *SafeString) Get() string { if p == nil { return "" } return *p } - 测试必须覆盖
nil输入路径:哪怕只是传nil进去看是否 panic 或返回合理错误
最麻烦的永远不是“有没有检查”,而是“检查了但没覆盖所有分支”,比如 defer 里用了某个指针,而那个指针在前面某条错误路径中根本没初始化。这种得靠代码走读+覆盖率工具盯住。










