Go 迭代器
Go 1.23 引入了一个重要的语言特性:for range 现在支持遍历函数 (range over func)。这意味着我们可以为自定义类型实现迭代器,像遍历切片、map 一样使用 for range。
什么是 range over func?
简单说,你可以编写一个生成器函数 ,它接收一个回调(通常命名为 yield),然后在内部将元素逐个传给 yield。之后,这个函数就可以直接放在 for range 后面使用。
// 一个简单的整数序列迭代器
func Count(n int) func(yield func(int) bool) {
return func(yield func(int) bool) {
for i := 0; i < n; i++ {
if !yield(i) {
return
}
}
}
}
func main() {
for v := range Count(5) {
fmt.Println(v)
}
}
// 输出 0 1 2 3 4
yield 返回 bool:返回 true 表示继续迭代,返回 false 表示提前终止(对应循环中的 break)。
两类迭代器:Seq 和 Seq2
标准库 iter 定义了两种迭代器类型:
-
iter.Seq[V any]:产生单个值的序列 →for v := range seq -
iter.Seq2[K, V any]:产生键值对的序列 →for k, v := range seq2
它们的底层都是函数类型:
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
推送式迭代器(Pushing Iterator)
推送式迭代器是主动 将元素"推"给回调函数。上面的 Count 就是推送式。Go 的 for range 循环体会被编译器转换成 yield 回调。
例如:
for v := range Count(5) {
fmt.Println(v)
}
// 等价于
Count(5)(func(v int) bool {
fmt.Println(v)
return true
})
拉取式迭代器(Pulling Iterator)
拉取式迭代器相反:由调用者 主动"拉"取下一个元素。标准库 iter.Pull 和 iter.Pull2 可以将推送式迭代器转换为拉取式:
next, stop := iter.Pull(Count(5))
defer stop()
for {
v, ok := next()
if !ok {
break
}
fmt.Println(v)
}
next() 返回元素和一个布尔值表示是否还有值。stop() 必须调用以释放资源(尤其在提前退出时)。
性能上,拉取式比推送式慢很多(见下文),一般优先使用推送式。
错误处理
如果需要处理错误,可以将 error 作为 yield 的第二个参数:
func ReadLines(r io.Reader) iter.Seq2[string, error] {
scanner := bufio.NewScanner(r)
return func(yield func(string, error) bool) {
for scanner.Scan() {
if !yield(scanner.Text(), scanner.Err()) {
return
}
}
}
}
func main() {
for line, err := range ReadLines(file) {
if err != nil {
log.Fatal(err)
}
fmt.Println(line)
}
}
标准库中的迭代器
Go 1.23 的 slices 和 maps 包新增了许多迭代器工具:
-
slices.All(s)→ 索引+值的Seq2 -
slices.Values(s)→ 只返回值的Seq -
slices.Chunk(s, n)→ 按块分割 -
slices.Collect(seq)→ 将迭代器收集为切片 -
maps.Keys(m)、maps.Values(m)、maps.All(m) -
maps.Collect(seq2)→ 收集为 map
示例:
s := []int{1,2,3,4,5}
for chunk := range slices.Chunk(s, 2) {
fmt.Println(chunk) // [1 2] [3 4] [5]
}
sum := 0
for v := range slices.Values(s) {
sum += v
}
链式调用
Go 的迭代器本身不支持方法链(因为返回的是函数,不是对象)。但可以自己封装一个小结构体来实现类似效果:
type SeqWrapper[E any] struct { seq iter.Seq[E] }
func (w SeqWrapper[E]) Map(f func(E) E) SeqWrapper[E] {
return SeqWrapper[E]{seq: func(yield func(E) bool) {
for v := range w.seq {
if !yield(f(v)) {
return
}
}
}}
}
func (w SeqWrapper[E]) Collect() []E {
return slices.Collect(w.seq)
}
// 使用
result := SeqWrapper[int]{seq: slices.Values([]int{1,2,3})}.
Map(func(x int) int { return x * 2 }).
Collect()
fmt.Println(result) // [2 4 6]
小结
-
Go 1.23 的
range over func让自定义迭代器成为可能。 -
核心是编写一个接受
yield回调的函数,用for range调用它。 -
标准库
slices/maps已经广泛支持迭代器,方便数据流处理。 -
推送式性能好,拉取式慢但有时必要。
-
这个特性虽然增加了少许复杂度,但大大提升了代码的表达力,值得尝试。