唯一可靠方式是s == nil;len(s) == 0无法区分nil与空切片,因两者长度均为0;interface{}或泛型中需用反射判断;json序列化时nil转null、空切片转[],语义不同。

怎么判断一个切片是 nil 还是空?
唯一可靠方式就是 s == nil。别用 len(s) == 0——它对两者都返回 true,完全无法区分。
常见错误现象:写 if len(tags) == 0 就认为“字段没传”,结果前端传了 {"tags": null}(解码为 nil)和 {"tags": []}(解码为非 nil 空切片),业务逻辑却一视同仁。
-
var s []int→s == nil为true -
s := []int{}或s := make([]int, 0)→s == nil恒为false - 若变量类型是
interface{}或泛型参数(如func IsNil[T any](v T)),v == nil编译失败;此时必须用反射:reflect.ValueOf(v).Kind() == reflect.Slice && reflect.ValueOf(v).IsNil()
JSON 序列化/反序列化时为什么一个变 null、一个变 []?
因为 json.Marshal 把语义当真:nil 切片表示“字段未设置/不存在”,空切片表示“明确设置了,但内容为空”。这是 API 兼容性高频翻车点。
示例:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
json.Marshal(struct{ Data []string }{Data: nil}) // → {"Data":null}
json.Marshal(struct{ Data []string }{Data: []string{}}) // → {"Data":[]}
反向解析时,前端传 {"tags": null} 和 {"tags": []},Go 解析后分别是 nil 和非 nil 切片——若只检查 len(tags) == 0,就漏掉了“客户端根本没传这个字段”的语义。
- 结构体字段定义为
Data []string,天然支持这种区分,标准库也按此约定行为 - ORM 映射(如 GORM)、gRPC 接口定义、OpenAPI schema 生成工具都依赖该语义
append 时表现一样,还要区分吗?
绝大多数时候 append 确实都安全,但“表现一样”不等于“底层一样”,容易在扩容策略和调试中埋坑。
-
append(nilSlice, x)总是分配新底层数组(cap=1起步) -
append(emptySlice, x)可能复用底层数组(如果cap > 0),但[]int{}的cap是0,所以首次append也必然分配 - 想预分配容量?用
make([]int, 0, 10),不是[]int{}——后者永远cap=0,后续每次append都可能触发 realloc - 别写
if cap(s) > 0这类逻辑:对nil切片,cap(s)是0,条件恒成立,纯属多余分配
函数参数或结构体字段该初始化成哪个?
取决于你想表达什么语义,不是性能问题,而是契约信号。
- 想表达“这个字段可选,没传就是没这个概念”,用
var s []T(即零值nil) - 想表达“这个容器已准备好,只是当前没数据”,用
s := []T{}或s := make([]T, 0) - 标准库倾向前者:
json.Unmarshal把 JSON 中的null解成nil切片,把[]解成空切片
最麻烦的地方在于:90% 的操作里它们真的一样,只有 JSON、反射、ORM 映射、C 交互等少数场景才暴露差异——等你踩到,往往已是线上 bug。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










