Go 泛型实战:用 constraints 写出可复用的容器与工具
test12026-09-140 次阅读
何时该上泛型
当你发现自己在为 int、string、自定义结构体各写一遍几乎相同的逻辑时,就是泛型出场的时候。泛型的价值是“一套实现,多种类型”,而不是为了炫技。Go 1.18 引入泛型后,容器、工具函数、仓储接口都能写得又安全又简洁。
一个类型安全的栈
package main
import "fmt"
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}
func main() {
var s Stack[int]
s.Push(1)
s.Push(2)
v, ok := s.Pop()
fmt.Println(v, ok) // 输出:2 true
}
复用逻辑:取 map 的 key 集合
这种“对任意 K/V 都一样的遍历逻辑”最适合写成泛型函数,避免每次手写循环。
func MapKeys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
// 调用处类型自动推断
keys := MapKeys(map[string]int{"a": 1, "b": 2}) // []string{"a", "b"}
用 constraints 约束数值类型
Go 1.21 起标准库 cmp 包提供了 cmp.Ordered,可以直接约束“可比较大小”的类型,用来写通用的求和、最值函数。
import "cmp"
func Sum[T cmp.Ordered](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// Sum([]int{1, 2, 3}) == 6
// Sum([]float64{1.5, 2.5}) == 4.0
泛型仓储接口的思路
用泛型定义仓储契约,不同实体各自实现,既统一了方法签名,又不丢失具体类型。
type Repository[T any] interface {
FindByID(id int64) (T, error)
Save(entity T) error
}
type UserRepo struct{ db *sql.DB }
func (r *UserRepo) FindByID(id int64) (User, error) { /* ... */ }
func (r *UserRepo) Save(u User) error { /* ... */ }
// UserRepo 自动满足 Repository[User]
经验清单
- 泛型解决“类型不同、逻辑相同”,接口解决“行为相同、实现各异”,两者互补。
- 不要为每一个小函数都加泛型,只有真正跨类型复用时才值得。
- 标准库 cmp.Ordered / constraints 比自己手写一堆类型分支更可靠。
- 泛型让编译期类型检查更严,等于把一部分运行时错误提前消灭。
T
test1
文章作者
何时该上泛型 当你发现自己在为 int、string、自定义结构体各写一遍几乎相同的逻辑时,就是泛型出场的时候。泛型的...
- 分类
- 技术
- 发布时间
- 2026-09-14
- 字数
- 约 1752 字
- 阅读
- 0 次
