一句话总结 :再也不用写
if str == "" { str = "default" }了!
Go 1.22 带来了一个看似低调、实则超实用的新函数:cmp.Or。
它藏在 golang.org/x/exp/cmp 包里(未来可能会进标准库),名字简单,功能却能让你少写一堆样板代码。
今天我们就来揭开它的神秘面纱,并用几个真实又接地气的小例子,看看它如何让你的 Go 代码更简洁、更优雅、更"懒人友好" 😎。
🤔 问题来了:你是不是也这样写过?
go
name := os.Getenv("USER_NAME")
if name == "" {
name = "anonymous"
}
或者处理多个备选值时:
go
id := slug
if id == "" {
id = internalID
}
if id == "" {
id = generateFallbackID()
}
这种"找第一个非空值"的逻辑,在配置加载、API 参数处理、日志打标等场景中遍地开花。但写多了,真的会手酸!
有没有一种方式,能让我们一行搞定 ?
答案就是:cmp.Or!
✨ cmp.Or 是什么?
它的定义超级简单(Go 1.22+):
go
// Or 返回第一个不等于零值的参数。
// 如果全都是零值,就返回该类型的零值。
func Or[T comparable](vals ...T) T
支持所有 comparable 类型:字符串、整数、指针、布尔值......甚至自定义结构体(只要字段可比较)!
💡 注意:目前需通过
golang.org/x/exp/cmp引入(截至 Go 1.24 仍未进入标准库cmp,但社区呼声很高)。
安装一下:
bash
go get golang.org/x/exp@latest
然后就能愉快使用:
go
import "golang.org/x/exp/cmp"
🧪 实战小例子:让代码"瘦身"成功!
✅ 场景 1:环境变量 + 默认值(最常用!)
go
port := cmp.Or(os.Getenv("PORT"), "8080")
dbURL := cmp.Or(os.Getenv("DATABASE_URL"), "sqlite://dev.db")
🎯 优势 :一行替代
if-else,清晰直观,还能链式扩展!
✅ 场景 2:多级 fallback(比如用户标识)
假设你有多个可能的用户名来源:
- JWT 里的
username - 请求头里的
X-User-ID - 最后 fallback 到
"guest"
传统写法:
go
var username string
if u := jwt.Username(); u != "" {
username = u
} else if u := req.Header.Get("X-User-ID"); u != "" {
username = u
} else {
username = "guest"
}
用 cmp.Or:
go
username := cmp.Or(
jwt.Username(),
req.Header.Get("X-User-ID"),
"guest",
)
🙌 代码行数减半,可读性翻倍!
✅ 场景 3:非空指针选择(前端传参常见)
假设你解析了一个 JSON,其中某个字段可能是 null:
go
type Request struct {
Name *string `json:"name"`
}
你想取 Name,但如果为空就用默认值:
go
defaultName := "Unknown"
actualName := cmp.Or(req.Name, &defaultName)
⚠️ 注意:这里
req.Name是*string,所以&defaultName也要是指针。
cmp.Or对指针同样有效------只要不是nil,就算"非零"!
✅ 场景 4:排序时的多级比较(高级玩法!)
Go 的 slices.SortFunc 需要返回 -1/0/1。通常我们会这样写:
go
slices.SortFunc(orders, func(a, b Order) int {
if c := cmp.Compare(a.Customer, b.Customer); c != 0 {
return c
}
if c := cmp.Compare(a.Product, b.Product); c != 0 {
return c
}
return cmp.Compare(b.Price, a.Price) // 高价优先
})
用 cmp.Or 可以简化成:
go
slices.SortFunc(orders, func(a, b Order) int {
return cmp.Or(
cmp.Compare(a.Customer, b.Customer),
cmp.Compare(a.Product, b.Product),
cmp.Compare(b.Price, a.Price),
)
})
🔥 虽然不能短路求值 (所有
Compare都会执行),但在数据量不大时,简洁性远胜微小性能损耗!
🚨 注意事项 & 小陷阱
-
仅限
comparable类型不能用于 slice、map、function 等不可比较类型。
-
零值判断是"严格等于"
""、0、false、nil都算零值。所以
cmp.Or(0, 1)会返回1,这符合预期。 -
没有短路求值
所有参数都会被求值。如果某个参数是昂贵函数调用,慎用!
go// ❌ 不推荐:expensiveFunc() 总是会被调用! result := cmp.Or(fastVal(), expensiveFunc())
🛠 小技巧:封装自己的 OrString
如果你只用字符串,可以封装一个更安全的版本(避免指针混淆):
go
func OrString(vals ...string) string {
for _, v := range vals {
if v != "" {
return v
}
}
return ""
}
但说实话......cmp.Or 已经够好用了,何必重复造轮子?🫠
🎁 彩蛋:为什么叫 Or?
"它就像 SQL 里的
COALESCE,或者 JavaScript 里的||(虽然 JS 的||有 truthy/falsy 问题)。"
而 Or 这个名字,是 Go 团队核心成员 Russ Cox 拍板的------简洁、准确、带点极客味。
✅ 总结:cmp.Or 值得加入你的工具箱!
| 场景 | 传统写法 | cmp.Or 写法 |
|---|---|---|
| 环境变量默认值 | if == "" |
cmp.Or(env, "default") |
| 多级 fallback | 多层 if-else |
一行链式调用 |
| 指针非空选择 | 手动判 nil |
自动跳过 nil |
| 多字段排序 | 嵌套 if |
cmp.Or(cmp.Compare(...)) |
记住:写代码不是为了"能跑",而是为了"好读、好改、好笑(减少 bug 笑话)"。
下次当你又要写 if x == "" { x = y } 时,不妨试试 cmp.Or ------
让默认值的选择,变得像呼吸一样自然。
📌 Bonus:如果你正在用 Go 1.22+,现在就去重构一段旧代码吧!你会发现,世界突然清爽了许多 🌈。