Go 1.23 的 iter.Seq 是唯一官方认可且与 for range 原生兼容的迭代器抽象,本质为 func(yield func(T) bool) error,适用于延迟计算、流式生成或封装外部数据源,普通切片/Map 应直接 for range。

Go 1.23 的 iter.Seq 是唯一值得用的原生迭代器抽象
Go 没有传统意义上的 Iterator 接口(比如 Java 的 Iterator<T>),强行模仿会写出反模式代码。从 Go 1.23 开始,iter.Seq 成为标准库中唯一被官方认可、且与 for range 原生兼容的迭代器类型。它本质是一个函数签名:func(yield func(T) bool) error,靠闭包捕获状态,不暴露内部结构。
常见错误是试图定义自己的 Iterator 接口并实现 Next()、HasNext() 方法——这会导致手动管理状态、无法用 for range、容易漏掉资源清理,还和 Go 的“少即是多”哲学冲突。
- 只在需要延迟计算、流式生成或封装外部数据源(如数据库游标、文件行读取)时才用
iter.Seq - 普通切片、map 遍历直接
for range,别包装 -
iter.Seq返回值必须是函数,不能是结构体;否则for range会报错:cannot range over … (type …)
如何把 slice 转成 iter.Seq 并安全遍历
不是所有集合都需要转——只有当你想隐藏底层是 slice 这一事实,或统一处理多种数据源(slice / channel / DB rows)时才有意义。转换的关键是让 yield 函数控制退出时机,避免越界或 panic。
示例:将 []int 封装为 iter.Seq[int]
立即学习“go语言免费学习笔记(深入)”;
func SliceSeq[T any](s []T) iter.Seq[T] {
return func(yield func(T) bool) error {
for _, v := range s {
if !yield(v) {
break
}
}
return nil
}
}
- yield 返回
false表示消费者提前终止(如break或return),此时必须break循环,否则继续执行可能浪费资源 - 不要在 yield 前做耗时操作(如网络请求),因为 yield 可能被跳过
- 如果底层是可变数据(如全局 slice),注意并发安全:yield 函数可能被多个 goroutine 同时调用,需加锁或复制快照
自定义迭代器遇到 invalid operation: cannot range over … 怎么办
这个错误几乎都源于返回值类型没对上 iter.Seq[T]。常见踩坑点:
- 写成了
func() iter.Seq[T](多套了一层函数)→ 应该直接返回iter.Seq[T] - 参数名写错,比如
func(yield func(T) bool) error写成func(yield func(T) bool) bool(返回值必须是error) - 用了泛型约束但没导出类型参数,导致调用处推导失败,编译器误报为“不可遍历”
- 在 Go < 1.23 环境下 import
golang.org/x/exp/iter,但没启用GOEXPERIMENT=loopvar,也会触发类似错误
为什么不用 channel 模拟迭代器
用 chan T 实现“迭代器”看似简单,但实际问题很多:
- channel 是并发原语,不是控制流抽象;
for range ch会阻塞直到 channel 关闭,无法中途退出(除非加额外信号 channel) - 内存占用高:未消费的元素会堆积在 buffer 中,而
iter.Seq是 pull-based,按需生成 - 关闭逻辑难管理:谁关?何时关?关早了丢数据,关晚了 goroutine 泄漏
- 和标准库生态脱节:
slices.Map、slices.Filter等函数只接受切片,不接受 channel
真正需要流式处理时,优先考虑 iter.Seq + iter.Seq2(双值迭代器),而不是 channel。
复杂点在于 yield 函数的调用时机完全由消费者控制,这意味着你的迭代逻辑必须是纯函数式或状态隔离的——任何隐式共享状态(比如闭包外的计数器)都可能在并发遍历时出错。这点容易被忽略。










