Go range over func 怎样接入回调迭代器
来源:17golang原创
时间:2026-10-21 22:50:46 117浏览 收藏
如果一个类型想把内部数据交给调用方遍历,Go 1.23 之后可以让方法返回 iter.Seq[T],调用方直接写 for v := range source.All()。关键不是把普通函数硬套进 range,而是让函数接收一个返回 bool 的 yield 回调:每次回调返回 true 才继续,返回 false 就停止推送。
- range over func 从 Go 1.23 开始支持,常用形态是
func(func(V) bool)。 - 优先用
iter.Seq表达单值序列,用iter.Seq2表达键值或索引值。 - 迭代器必须尊重 yield 的
false,一次性数据源还要在返回前完成清理。
先把回调迭代器的边界说清楚
Go 1.23 的 for range 新增了三种函数范围表达式:func(func() bool)、func(func(K) bool) 和 func(func(K, V) bool)。它们分别对应无值、单值和双值迭代。标准库 iter 用 Seq、Seq2 给这两种常见形态起了可复用的类型名。
这里的迭代器属于 push 方式:数据源主动调用 yield,而不是调用方反复调用 next。这个区别很重要,因为 break 最终会让 yield 返回 false,数据源需要立刻停止自己的循环。

用 iter.Seq 接入一个可复用的 All 方法
下面的类型把内部切片封装起来,只公开一个返回 iter.Seq 的 All 方法。代码中的注释说明了继续条件和资源边界;调用者不需要知道切片字段叫什么。
package main
import (
"fmt"
"iter"
)
type Catalog struct {
items []string
}
// All 按当前顺序推送目录项;yield 返回 false 时立即停止。
func (c *Catalog) All() iter.Seq[string] {
return func(yield func(string) bool) {
for _, item := range c.items {
// 把停止权交给消费方,避免无意义地继续遍历。
if !yield(item) {
return
}
}
}
}
func main() {
catalog := &Catalog{items: []string{"Go", "Rust", "Python"}}
for item := range catalog.All() {
fmt.Println(item)
// 找到目标后 break,会让迭代器收到 false。
if item == "Rust" {
break
}
}
}
All 方法名也有约定意义:如果一个集合只有一种自然遍历方式,使用 All 比暴露具体存储结构更容易让调用者形成稳定预期。若有倒序、过滤或范围扫描,可以用 Backward、Where、Scan 等名称明确序列来源。
提前停止不是装饰:必须让 yield 的 false 生效
最常见的错误是无视 yield 的返回值,继续读取文件、拉取分页数据或向下游发送记录。对内存切片来说只是多做几次循环;对网络流或数据库游标来说,可能造成额外 I/O,甚至让上游 goroutine 一直等待。
如果迭代器包裹的是一次性资源,建议把关闭动作放进迭代器函数的 defer,并在 yield 返回 false 时离开函数。这样普通遍历走完整条路径,break 也能触发同一套清理逻辑。
// Lines 返回一次性行流;调用方提前 break 时仍会关闭资源。
func Lines(open func() (interface{ ReadLine() (string, bool); Close() error })) iter.Seq[string] {
return func(yield func(string) bool) {
stream := open()
defer stream.Close() // 迭代结束或提前停止都释放资源。
for {
line, ok := stream.ReadLine()
if !ok {
return // 数据源耗尽,正常结束。
}
if !yield(line) {
return // 消费方 break,停止继续读取。
}
}
}
}
示例用接口省略了具体文件实现,重点是生命周期:打开、推送、停止、关闭必须属于同一个迭代器边界。生产代码还应把读取错误设计进元素类型或配套错误通道,不能把错误静默当成“没有下一条”。

Seq2、版本门槛和选型清单
需要同时返回键和值时,使用 iter.Seq2[K, V],例如遍历配置项、索引和值或映射表。它的 yield 形态是 func(K, V) bool,消费方可以直接写 for key, value := range source.All()。不要为了兼容旧代码把两个字段拼成字符串,那会损失类型信息。
| 场景 | 推荐形态 | 注意点 |
|---|---|---|
| 单值集合 | iter.Seq[T] | yield false 要停止内部循环 |
| 键值或索引值 | iter.Seq2[K,V] | 保持两个值的类型安全 |
| 需要逐个 next | iter.Pull(seq) | 不再是直接 range,结束前调用 stop |
| Go 1.22 及更早 | 回调方法或显式 next 循环 | 不能直接使用 range over func 语法 |
如果项目的 go.mod 最低版本低于 1.23,应先决定是否提升工具链;不能只替换一行语法。暂时不能升级时,保留原有的 Visit(func(T) bool) 回调接口通常更稳,等最低版本调整后再提供 All() iter.Seq[T] 适配层。
常见问题
range over func 能遍历任意函数吗?
不能。函数必须匹配三种规定形态之一,最常见的是接收 func(V) bool 的单参数函数。
for 循环里的 break 会不会自动停止数据源?
会。编译器通过 yield 的返回值把停止信号传回迭代器;前提是迭代器检查 false 并及时 return。
什么时候应该用 iter.Pull?
需要把两个迭代器并行推进、逐个读取或桥接旧式 next API 时再用 Pull;普通顺序遍历优先直接 range。
把回调函数设计成 iter.Seq 后,调用方获得了简洁的 range 语法,数据源仍保留对停止和清理的控制。真正落地时先确认 Go 1.23 版本门槛,再检查 yield 的 false、一次性资源关闭和错误传播这三个边界。
-
152 收藏
-
151 收藏
-
101 收藏
-
323 收藏
-
428 收藏
-
192 收藏
-
312 收藏
-
371 收藏
-
409 收藏
-
291 收藏
-
371 收藏
-
352 收藏
-
246 收藏
-
436 收藏
-
501 收藏
-
369 收藏
-
266 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习