登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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,数据源需要立刻停止自己的循环。

Go range over func 中 iter.Seq、yield 回调与 for range 的关系说明图
图1:Go range over func 的回调关系说明图,展示 iter.Seq 向 yield 推送元素,不是运行截图。

用 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,停止继续读取。
            }
        }
    }
}

示例用接口省略了具体文件实现,重点是生命周期:打开、推送、停止、关闭必须属于同一个迭代器边界。生产代码还应把读取错误设计进元素类型或配套错误通道,不能把错误静默当成“没有下一条”。

Go range over func 在 break 后让 yield 返回 false 并释放资源的边界说明图
图2:提前 break 后的停止与清理边界说明图,展示 false 返回和资源关闭的关系,不是运行截图。

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]保持两个值的类型安全
需要逐个 nextiter.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、一次性资源关闭和错误传播这三个边界。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>