Go 1.23 中 for range 只认 iter.Seq[T],因其是编译器硬编码唯一支持的函数类型 func(yield func(T) bool) error;自定义结构体、接口或签名不符的函数均触发 cannot range over 错误。

Go 1.23 的 iter.Seq[T] 是唯一能直接用于 for range 的迭代器类型;其他所有自定义结构体、接口或错误签名的函数,都会触发 cannot range over ... 编译错误。
为什么 for range 只认 iter.Seq[T],不认你的 Iterator 结构体?
Go 没有“可迭代协议”接口(比如 Python 的 __iter__ 或 Java 的 Iterable),for range 的支持是硬编码在编译器里的:它只接受一种函数签名 —— func(yield func(T) bool) error。这个类型被标准库定义为 iter.Seq[T],它不是接口,也不是 struct,就是一个具名函数类型。
常见误操作包括:
- 定义
type MyIterator struct { ... }并实现Next()/HasNext()——for range完全无视,报错cannot range over ... (type MyIterator) - 写成
func() iter.Seq[T](返回一个函数)——for range拿到的是func(),不是iter.Seq[T],同样报错 - 把 yield 回调写成
func(T) bool但函数整体返回bool而非error—— 签名不匹配,编译失败
iter.Seq[T] 的 yield 函数必须立刻响应 false
yield 不是“通知我该停了”,而是“消费者已明确终止遍历”——比如用户在 for range 循环里写了 break 或 return,运行时就会让 yield 返回 false。此时你的迭代器函数必须立即退出,不能再访问底层数据、不能继续调用 yield、也不能做任何副作用操作。
立即学习“go语言免费学习笔记(深入)”;
典型错误写法:
for i := 0; i < len(s); i++ {
if !yield(s[i]) {
// ❌ 错误:没 break,i 还会自增,下一轮可能 panic
}
}
正确写法:
for _, v := range s {
if !yield(v) {
break // ✅ 必须 break,且 yield 后不能再有任何逻辑
}
}
什么场景才该用 iter.Seq[T]?别包装切片和 map
普通 []T、map[K]V、string、chan T 原生支持 for range,性能最好、语义最直白。强行套一层 iter.Seq[T] 只会引入闭包开销、掩盖数据来源、还可能引发并发问题(比如多个 goroutine 同时调用同一个 iter.Seq,而底层是共享 slice)。
真正值得封装的只有三类:
-
外部流式数据源:如
*sql.Rows、逐行读取的*os.File、HTTPResponse.Body - 延迟计算序列:如无限斐波那契、按需生成的 UUID 流、分页 API 的 token 驱动迭代
-
统一多种底层类型:让
[]T、chan T、*sql.Rows都实现同一签名,供上层处理逻辑复用
并发调用 iter.Seq[T] 时,状态安全谁来管?
iter.Seq[T] 本身不带任何同步机制。如果你的迭代器闭包捕获了可变状态(例如全局 map、未加锁的计数器、共享的游标变量),而它又被多个 goroutine 同时用于 for range,结果不可预测。
解决方案取决于使用方式:
- 如果每次调用都新建一个
iter.Seq[T](推荐),状态天然隔离,无需加锁 - 如果复用同一个
iter.Seq[T]实例(不推荐),必须自己加sync.Mutex或改用不可变快照(如copy切片) - 切忌在
yield调用前做网络请求或磁盘 I/O —— 因为yield可能返回false,这些操作就白做了
最易被忽略的一点:yield 的返回值不是“要不要继续”,而是“消费者已决定停止”。你不能把它当成控制流条件去轮询,而要当作一次性信号来响应。


















