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、也不能做任何副作用操作。
典型错误写法:
for i := 0; i
<p>正确写法:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6460" title="Golang Naming"><img
src="https://img.php.cn/upload/skill/000/000/081/179094616043400.jpg" alt="Golang Naming" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="overflowclass">Golang Naming</a>
<p class="overflowclass">Go(Golang)命名规范 — 包括包、构造函数、结构体、接口、常量、枚举、错误、布尔值、接收器、getter/setter、函数等。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<pre class="brush:php;toolbar:false;">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 的返回值不是“要不要继续”,而是“消费者已决定停止”。你不能把它当成控制流条件去轮询,而要当作一次性信号来响应。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










