Go 泛型时代的新宠:标准库新增的泛型包全解析
自从 Go 1.18 正式引入泛型以来,Go 官方团队没有停下脚步,而是持续在后续版本中为标准库添加了一系列基于泛型的新包和方法。这些新特性让我们的代码更加简洁、类型安全,也减少了大量样板代码。
今天就来系统梳理一下这些新成员,包括它们的引入版本、基本用法,以及与老写法的对比。
一、slices 包(Go 1.21)
在 Go 1.21 之前,操作切片往往需要手写循环或依赖第三方库。现在,slices 包提供了一整套泛型函数。
主要函数
| 函数 | 说明 |
|---|---|
slices.Sort |
原地排序 |
slices.Sorted |
排序并返回新切片 |
slices.BinarySearch |
二分查找 |
slices.Contains |
判断元素是否存在 |
slices.Index |
查找元素位置 |
slices.Delete |
删除元素 |
slices.Clone |
克隆切片 |
slices.Concat |
拼接多个切片(1.22+) |
slices.Equal |
比较两个切片 |
slices.Insert |
插入元素 |
新老写法对比
老写法(手动实现):
// 判断切片是否包含某元素
func containsInt(s []int, v int) bool {
for _, x := range s {
if x == v {
return true
}
}
return false
}
// 删除指定位置的元素
func deleteInt(s []int, i int) []int {
return append(s[:i], s[i+1:]...)
}
新写法(Go 1.21+):
import "slices"
// 一行搞定,且支持任意可比较类型
ok := slices.Contains([]int{1, 2, 3}, 2) // true
ok := slices.Contains([]string{"a", "b"}, "a") // true
// 安全删除,自动处理边界
s := []int{1, 2, 3, 4}
s = slices.Delete(s, 1, 2) // [1, 3, 4]
排序对比:
// 老写法
import "sort"
ints := []int{3, 1, 2}
sort.Ints(ints) // 只能用于 int
// 新写法 - 泛型支持
import "slices"
slices.Sort([]int{3, 1, 2}) // int
slices.Sort([]float64{3.1, 1.2}) // float64
slices.Sort([]string{"c", "a"}) // string
// 使用自定义比较器
type Person struct { Name string; Age int }
people := []Person{{"Alice", 30}, {"Bob", 25}}
slices.SortFunc(people, func(a, b Person) int {
return cmp.Compare(a.Age, b.Age)
})
二、maps 包(Go 1.21)
与 slices 包同时引入,maps 包提供了 map 的常用操作函数。
主要函数
| 函数 | 说明 |
|---|---|
maps.Clone |
克隆 map |
maps.Copy |
复制所有键值对 |
maps.DeleteFunc |
按条件删除 |
maps.Equal |
比较两个 map |
maps.Keys |
返回所有 key(1.23+ 可迭代) |
maps.Values |
返回所有 value(1.23+ 可迭代) |
新老写法对比
克隆 map:
// 老写法 - 手动循环
func cloneMap[K comparable, V any](m map[K]V) map[K]V {
result := make(map[K]V, len(m))
for k, v := range m {
result[k] = v
}
return result
}
// 新写法 - 一行搞定
import "maps"
original := map[string]int{"a": 1, "b": 2}
cloned := maps.Clone(original)
删除满足条件的元素:
m := map[int]string{1: "a", 2: "bb", 3: "ccc"}
// 老写法 - 循环中删除(需注意并发安全问题)
for k, v := range m {
if len(v) > 1 {
delete(m, k)
}
}
// 新写法 - 一行搞定
maps.DeleteFunc(m, func(k int, v string) bool {
return len(v) > 1
})
三、cmp 包(Go 1.21)
cmp 包虽然小巧,但解决了泛型时代比较操作的痛点。
主要函数
// Ordered 约束:支持 < > <= >= 的类型
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
// Compare 返回 -1, 0, 1
func Compare[T Ordered](x, y T) int
// Less 返回 x < y
func Less[T Ordered](x, y T) bool
新老写法对比
// 老写法 - 每种类型都要写一遍
func compareInt(a, b int) int {
if a < b {
return -1
} else if a > b {
return 1
}
return 0
}
// 新写法 - 泛型通用
import "cmp"
result := cmp.Compare(3, 5) // -1
result := cmp.Compare(3.14, 2.71) // 1
result := cmp.Compare("abc", "abd") // -1
配合 slices.SortFunc 使用:
type Student struct {
Name string
Score float64
}
students := []Student{
{"Alice", 95.5},
{"Bob", 87.0},
{"Charlie", 92.3},
}
// 按分数降序
slices.SortFunc(students, func(a, b Student) int {
return cmp.Compare(b.Score, a.Score) // 注意 b 在前
})
四、iter 包(Go 1.23)
Go 1.23 引入了革命性的迭代器特性,iter 包定义了迭代器的核心类型。
核心类型
// Pull 迭代器(简化版)
type Seq[V any] func(yield func(V) bool)
// Pull 迭代器(带 key)
type Seq2[K, V any] func(yield func(K, V) bool)
迭代器的威力
Go 1.23 可以在 for range 中直接使用函数迭代器:
// 自定义迭代器
func Backward(s []int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := len(s) - 1; i >= 0; i-- {
if !yield(s[i]) {
return
}
}
}
}
// 使用迭代器
for v := range Backward([]int{1, 2, 3}) {
fmt.Println(v) // 3, 2, 1
}
slices/maps 包的迭代器增强(Go 1.23)
// 获取所有 key 作为迭代器
m := map[string]int{"a": 1, "b": 2}
for k := range maps.Keys(m) {
fmt.Println(k)
}
// 收集迭代器结果
keys := slices.Collect(maps.Keys(m)) // []string{"a", "b"}
values := slices.Collect(maps.Values(m)) // []int{1, 2}
// 链式操作
allValues := slices.Collect(maps.Values(largeMap))
filtered := slices.Collect(slices.Values(allValues))
五、sync.OnceValue / sync.OnceFunc / sync.OnceValues(Go 1.21)
sync.Once 是老朋友了,但每次使用都要写一堆样板代码。Go 1.21 新增的泛型版本让单例模式变得优雅。
新老写法对比
老写法 - sync.Once:
type Server struct {
dbOnce sync.Once
db *sql.DB
dbErr error
}
func (s Server) DB() (sql.DB, error) {
s.dbOnce.Do(func() {
s.db, s.dbErr = sql.Open("sqlite3", "file.db")
})
return s.db, s.dbErr
}
新写法 - sync.OnceValue:
type Server struct {
db func() *sql.DB
}
func NewServer() *Server {
return &Server{
db: sync.OnceValue(func() *sql.DB {
conn, _ := sql.Open("sqlite3", "file.db")
return conn
}),
}
}
// 直接调用,自动保证只执行一次
s.db() // 返回 *sql.DB
三个函数的区别:
| 函数 | 返回值 | 使用场景 |
|---|---|---|
sync.OnceFunc |
无返回值 | 只需执行一次的操作 |
sync.OnceValue[T] |
返回单个值 | 计算并缓存单个结果 |
sync.OnceValues[T1, T2] |
返回两个值 | 返回结果+错误等 |
// OnceFunc - 只执行一次
var initOnce = sync.OnceFunc(func() {
log.Println("初始化完成")
})
// OnceValue - 缓存单个返回值
var getConfig = sync.OnceValue(func() *Config {
return loadConfigFromFile()
})
// OnceValues - 返回值+错误
var readFile = sync.OnceValues(func() ([]byte, error) {
return os.ReadFile("data.txt")
})
// 使用
config := getConfig() // *Config
data, err := readFile() // ([]byte, error)
六、其他值得关注的泛型特性
slices.Concat(Go 1.22)
// 老写法
combined := append(append([]int{}, a...), b...)
// 新写法
combined := slices.Concat(a, b) // 支持任意多个切片
combined := slices.Concat(a, b, c, d)
slices.Repeat(Go 1.24)
// 创建包含重复元素的切片
s := slices.Repeat([]int{1, 2}, 3) // [1, 2, 1, 2, 1, 2]
maps.Collect(Go 1.24)
// 从迭代器创建 map
m := maps.Collect(iter.Seq2[K, V])
总结表格
| 包/方法 | 版本 | 用途 |
|---|---|---|
slices 包 |
1.21 | 切片泛型操作 |
maps 包 |
1.21 | Map 泛型操作 |
cmp 包 |
1.21 | 有序类型比较 |
sync.OnceFunc |
1.21 | 单次执行(无返回) |
sync.OnceValue |
1.21 | 单次执行(单返回值) |
sync.OnceValues |
1.21 | 单次执行(双返回值) |
iter 包 |
1.23 | 迭代器类型定义 |
for range 函数 |
1.23 | 遍历函数迭代器 |
slices.Concat |
1.22 | 拼接多个切片 |
maps.Keys 迭代器 |
1.23 | 返回 key 迭代器 |
maps.Values 迭代器 |
1.23 | 返回 value 迭代器 |
写在最后
泛型引入后,Go 标准库正在稳步"现代化"。这些新包和方法不仅减少了样板代码,还提高了类型安全性和代码可读性。
不过要注意:
如果你还在用 Go 1.20 或更早版本,是时候考虑升级了。这些新特性真的能让你的代码更优雅!
---
参考资料: