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 同时为 slices 和 maps 包添加了大量迭代器相关函数:
// 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.Collect、slices.Chunk、maps.Keys 等函数,日常的集合操作变得更加流畅。