Go语言:iter包与新的迭代器模式

Go 1.23 引入了 range over func 语法和 iter 标准库包,这是 Go 语言在迭代器模式上的一次重要演进。本文梳理新迭代器的设计思路和使用方式。

一、为什么需要新的迭代器

Go 之前的迭代方式很有限:range 只支持 array、slice、map、string、channel 这几种内置类型。如果你有一个自定义数据结构(比如树、链表、数据库结果集),想让用户用 for range 遍历,做不到——要么返回 slice(全部加载到内存),要么提供 Next() 方法让用户写 for 循环手动调用。

Go 1.23 的方案是:让 range 支持以函数作为迭代目标。

二、iter.Seq 和 iter.Seq2

iter 包定义了两个核心类型:

package iter

// 产出单个值的迭代器
type Seq[V any] func(yield func(V) bool)

// 产出键值对的迭代器
type Seq2[K, V any] func(yield func(K, V) bool)

Seq[V] 是一个函数,它接收一个 yield 回调。迭代器通过反复调用 yield 来产出值,如果 yield 返回 false,表示消费者不再需要更多值(比如 break 了),迭代器应该停止。

三、基本使用

创建迭代器

package main

import (
    "fmt"
    "iter"
)

// 生成 [start, end) 范围内的整数
func Range(start, end int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := start; i < end; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

func main() {
    // 直接用 for range 遍历
    for v := range Range(1, 6) {
        fmt.Println(v) // 1 2 3 4 5
    }
}

键值对迭代器

// 遍历 map 并按 key 排序输出
func SortedMap[K cmp.Ordered, V any](m map[K]V) iter.Seq2[K, V] {
    return func(yield func(K, V) bool) {
        keys := make([]K, 0, len(m))
        for k := range m {
            keys = append(keys, k)
        }
        slices.Sort(keys)
        for _, k := range keys {
            if !yield(k, m[k]) {
                return
            }
        }
    }
}

func main() {
    m := map[string]int{"banana": 2, "apple": 5, "cherry": 1}
    for k, v := range SortedMap(m) {
        fmt.Printf("%s: %d\n", k, v)
    }
    // apple: 5
    // banana: 2
    // cherry: 1
}

四、标准库适配

Go 1.23 同时为 slicesmaps 包添加了大量迭代器相关函数:

// slices 包新增
func slices.All[S ~[]E, E any](s S) iter.Seq2[int, E]       // 索引+值
func slices.Values[S ~[]E, E any](s S) iter.Seq[E]          // 仅值
func slices.Chunk[S ~[]E, E any](s S, n int) iter.Seq[S]    // 分块
func slices.Sorted[E cmp.Ordered](seq iter.Seq[E]) []E      // 收集并排序
func slices.Collect[E any](seq iter.Seq[E]) []E              // 收集为 slice

// maps 包新增
func maps.Keys[M ~map[K]V, K comparable, V any](m M) iter.Seq[K]
func maps.Values[M ~map[K]V, K comparable, V any](m M) iter.Seq[V]
func maps.All[M ~map[K]V, K comparable, V any](m M) iter.Seq2[K, V]
func maps.Collect[K comparable, V any](seq iter.Seq2[K, V]) map[K]V

使用示例:

nums := []int{3, 1, 4, 1, 5, 9, 2, 6}

// 分块处理
for chunk := range slices.Chunk(nums, 3) {
    fmt.Println(chunk)
}
// [3 1 4]
// [1 5 9]
// [2 6]

// 收集迭代器结果为 slice
evens := slices.Collect(Filter(slices.Values(nums), func(n int) bool {
    return n%2 == 0
}))
fmt.Println(evens) // [4 2 6]

五、自定义迭代器的组合

迭代器的核心价值在于可组合。下面实现几个常用的组合器:

// Filter 过滤
func Filter[V any](seq iter.Seq[V], pred func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if pred(v) {
                if !yield(v) {
                    return
                }
            }
        }
    }
}

// Map 映射
func Map[V, R any](seq iter.Seq[V], f func(V) R) iter.Seq[R] {
    return func(yield func(R) bool) {
        for v := range seq {
            if !yield(f(v)) {
                return
            }
        }
    }
}

// Take 取前 n 个
func Take[V any](seq iter.Seq[V], n int) iter.Seq[V] {
    return func(yield func(V) bool) {
        count := 0
        for v := range seq {
            if count >= n {
                return
            }
            if !yield(v) {
                return
            }
            count++
        }
    }
}

链式调用:

// 从无限序列中取前 10 个偶数的平方
result := slices.Collect(
    Take(
        Map(
            Filter(Range(1, 1000), func(n int) bool { return n%2 == 0 }),
            func(n int) int { return n * n },
        ),
        10,
    ),
)
fmt.Println(result) // [4 16 36 64 100 144 196 256 324 400]

六、实际应用:数据库结果集

一个更贴近实际的例子——封装数据库查询结果为迭代器:

type User struct {
    ID   int
    Name string
}

func QueryUsers(db *sql.DB, query string, args ...any) iter.Seq2[User, error] {
    return func(yield func(User, error) bool) {
        rows, err := db.Query(query, args...)
        if err != nil {
            yield(User{}, err)
            return
        }
        defer rows.Close()

        for rows.Next() {
            var u User
            if err := rows.Scan(&u.ID, &u.Name); err != nil {
                if !yield(User{}, err) {
                    return
                }
                continue
            }
            if !yield(u, nil) {
                return
            }
        }
        if err := rows.Err(); err != nil {
            yield(User{}, err)
        }
    }
}

// 使用
for user, err := range QueryUsers(db, "SELECT id, name FROM users WHERE active = ?", true) {
    if err != nil {
        log.Printf("error: %v", err)
        continue
    }
    fmt.Printf("User: %s\n", user.Name)
}

相比返回 []User,迭代器方案的优势是流式处理,不需要把所有结果都加载到内存。当结果集很大时,这个差异很显著。

总结

iter 包的设计非常 Go——用最简单的函数签名表达迭代器语义,没有引入新的关键字或复杂的类型系统特性。Seq 本质上就是一个回调函数,理解成本很低。配合标准库的 slices.Collectslices.Chunkmaps.Keys 等函数,日常的集合操作变得更加流畅。