iter.seq 是 go 1.23 唯一官方认可且支持 for range 的迭代器抽象,其 func(yield func(t) bool) error 签名旨在解决控制权移交、提前终止和零分配遍历问题,不依赖 channel,无 goroutine 开销,适用于惰性数据源、统一接口或隐藏实现细节场景。

iter.Seq 是 Go 1.23 唯一被官方认可、且能直接 for range 的迭代器抽象,不是“可选方案”,而是当前唯一正统路径。
为什么必须用 func(yield func(T) bool) error 这个签名
这个函数签名不是设计癖好,而是为了解决三个实际问题:控制权移交、提前终止、零分配遍历。
-
yield是调用方提供的回调,由它决定“要不要继续”,所以遍历逻辑不锁死在迭代器内部 - 当
yield(v)返回false(比如用户写了break),迭代器必须立刻break循环,否则可能浪费 CPU 或触发副作用 - 整个结构不依赖
chan,没有 goroutine 开销,也不需要手动close,适合高频、短生命周期遍历 - 返回
error而非bool,是为了兼容 I/O 类场景(如文件读取失败、DB 查询中断)
cannot range over ... (type ...) 错误的 4 个常见原因
这个编译错误几乎全是类型不匹配导致的,不是语法错,是类型系统在拦你。
- 返回值写成
func() iter.Seq[T]—— 多套了一层函数,正确应直接返回iter.Seq[T] -
yield参数名或类型写错,例如写成func(val T) bool(少yield名)或func(T) int(返回值不是bool) - 函数返回类型写成
func(yield func(T) bool) bool,但iter.Seq[T]要求返回error - 泛型参数未导出或约束太强,导致调用处无法推导
T,编译器退化为“未知类型”,进而报不可遍历
什么时候真该用 iter.Seq,而不是直接 for range
别为了“用新特性”而用。只有满足以下任一条件,才值得封装:
- 数据源是惰性的:比如从数据库游标逐行拉、从文件按块解码、生成斐波那契数列
- 需要统一接口:把
[]T、chan T、*sql.Rows都转成同一类型供上层消费 - 要隐藏底层结构:比如你提供 SDK,不希望用户知道内部用的是 map 还是 slice
- 普通切片、map、数组?直接
for range,别包 —— 包了反而多一次函数调用、少编译器优化机会
把 []int 安全转成 iter.Seq[int] 的最小可行写法
注意边界检查、提前退出和并发安全这三点,缺一不可。
func SliceSeq[T any](s []T) iter.Seq[T] {
return func(yield func(T) bool) error {
for _, v := range s {
if !yield(v) {
break // 必须 break,不能 continue
}
}
return nil
}
}
- 不要在
yield(v)前做任何耗时操作(如 HTTP 请求、加锁),因为yield可能被跳过 - 如果
s是全局变量或可能被并发修改,应在闭包内copy一份快照,否则遍历结果不可预测 - 这个函数返回的是函数值,不是结构体 —— 如果你定义了
type MySeq struct { ... }并实现同签名方法,for range仍会报错
最容易被忽略的点:很多人以为 iter.Seq 是“更高级的 for range”,其实它是“让 for range 能用在原本不能用的地方”。它不替代 range,而是扩展 range 的能力边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











