结论:cmp.Compare仅适用于同类型可比较值的严格相等判断,返回-1/0/1;结构体、浮点容差、nil安全、字段筛选等场景必须用cmp.Equal配合cmp.Option。

直接说结论:用 cmp.Compare 替代手写比较逻辑,只适用于「两个同类型、可比较(comparable)的值」;一旦涉及结构体字段筛选、浮点容差、nil 安全或自定义排序规则,就必须用 cmp.Equal 配合 cmp.Option,而不是硬套 cmp.Compare。
什么时候能用 cmp.Compare?
它本质是 cmp.Equal 的简化子集:只做“严格相等/不等”判断,返回 -1 / 0 / 1,和 strings.Compare 行为一致。但它的输入限制比你想的更严:
- 两个值必须是同一基础类型(
int、string、bool等),且不能含不可比较字段(比如map、func、[]byte) - 不支持结构体字段忽略(哪怕你只想比一个字段也不行)
- 对
float64是逐位比较,NaN != NaN,没有容差机制 - 传入
nil指针会 panic,而cmp.Equal默认能安全处理
示例:下面这段代码看似合理,实则危险:
cmp.Compare(x, y) // x 和 y 是 *MyStruct 类型 —— panic: invalid operation: comparing pointer
结构体比较必须用 cmp.Equal + cmp.Option
手写 func(a, b T) int 最常出现在结构体排序或断言中,但 cmp.Compare 根本不接受结构体——它连字段访问都不支持。正确路径是用 cmp.Equal 判断相等性,再结合 sort.Slice 自定义排序逻辑:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 要忽略某个字段:用
cmp.FilterPath+cmp.Ignore - 要浮点容差:用
cmp.Comparer包裹float64比较函数 - 要 nil 安全指针比较:加
cmp.AllowUnexported或自定义 comparer - 要按某字段升序、另一字段降序:别碰
cmp.Compare,老实用sort.Slice
例如忽略结构体中的时间戳字段:
cmp.Equal(a, b, cmp.FilterPath(func(p cmp.Path) bool {
return p.String() == "MyStruct.Timestamp"
}, cmp.Ignore))
cmp.Compare 和 cmp.Equal 性能差异很小,但语义完全不同
很多人以为 cmp.Compare 更快,其实不是重点。它和 cmp.Equal 底层都走反射+选项链,性能差距在纳秒级。真正区别是用途:
-
cmp.Compare:只适合替代strings.Compare这类原生比较函数,且仅用于comparable类型 -
cmp.Equal:面向测试、断言、diff 场景,可扩展性强,是 go-cmp 的核心入口 - 误把
cmp.Equal当成cmp.Compare用(比如传两个结构体想得到 -1/0/1)——会编译失败,因为类型不匹配
错误写法:
if cmp.Compare(user1, user2) < 0 { ... } // user1/user2 是 struct 类型 → 编译报错
复杂点在于:很多人想用 cmp 统一所有比较逻辑,结果发现 Compare 太窄、Equal 返回布尔值又没法直接用于排序。这时候得接受一个事实——Go 没有“通用三路比较器”,该写 sort.Slice 还是得写,cmp 包只负责“相等性”这件事。

















