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

Go iter.Pull 怎样处理空值和异常分支

来源:17golang原创

时间:2026-11-03 18:45:47 483浏览 收藏

使用 iter.Pull 时,先用 next 返回的 ok 判断是否取到了有效值,不能把零值当作耗尽信号;创建 next、stop 后立即 defer stop(),让提前返回也能通知迭代器结束。nil 迭代器不是空序列,建议在自己的入口显式拒绝。业务错误应放进条目或其他明确通道,不应把所有失败都伪装成 ok=false。

下面从零做一个小型整数收集器:输入是一组文本,输出最多 N 个整数。它允许合法数字0,拒绝未初始化的数据源,将空文本的解析失败作为业务错误,并在达到上限时停止取数。示例只用内存数据,便于把结束语义讲清楚。

官方文档:https://pkg.go.dev/iter#Pull

版本说明:https://go.dev/doc/go1.23

1. 项目目标:给结束、零值和失败各一个位置

Pull 把推送式迭代器转换成主动拉取接口。它提供的布尔值只说明这次返回的值是否有效;当序列结束时,得到元素类型的零值与 false。我们用 Item 同时携带整数和 error,把“取到一条失败记录”与“来源没有下一条”区分开。

收集器的合同是:成功返回已取到的前缀,最多 N 项;任何已经观察到的业务错误都返回 nil 与 error,不交付部分结果。这个选择适合把结果一次性交给下游的调用方,也意味着达到 N 项后不会继续检查后续输入。它不是全量文件校验器。

拉取有效性与整数条目错误字段的静态结构
图1:原创静态结构图。拉取结果分组区分条目和有效标志,条目内容分组区分整数与业务错误,避免用0代表结束。

2. 环境准备:Go 1.23 以上,先写状态表

iter 随 Go 1.23 进入标准库。将完整程序保存为 main.go,使用 Go 1.23 或以上工具链即可使用这里的接口。程序不访问数据库、不打开文件,也不依赖第三方包;本文未执行程序或测试。

情况本项目处理是否业务失败
seq 为 nil创建 Pull 前拒绝是,入口参数错误
来源函数存在但不 yield正常返回空结果否
有效整数0ok 为 true 时照常收集否
空文本或非法数字Item.Err 携带解析错误是
达到读取上限停止收集并执行 stop否

limit 限定在0到1000之间是这个小工具的参数策略,不是 Pull 的限制。limit 为0时不调用 next,直接返回空结果;没有读取也就没有验证来源中的业务内容。

3. 核心代码:消费者负责 stop,生产者尊重 yield

package main

import (
    "fmt"
    "iter"
    "strconv"
)

type Item struct {
    Value int
    Err   error
}

func Numbers(tokens []string) iter.Seq[Item] {
    return func(yield func(Item) bool) {
        for i, token := range tokens {
            value, err := strconv.Atoi(token)
            if err != nil {
                // 业务失败作为一个显式条目交给消费者,然后结束来源。
                yield(Item{Err: fmt.Errorf("第%d项解析失败: %w", i+1, err)})
                return
            }
            if !yield(Item{Value: value}) {
                return
            }
        }
    }
}

func Take(seq iter.Seq[Item], limit int) ([]int, error) {
    if seq == nil {
        return nil, fmt.Errorf("数据源未初始化")
    }
    if limit  1000 {
        return nil, fmt.Errorf("读取数量必须在0到1000之间")
    }
    next, stop := iter.Pull(seq)
    defer stop() // 达到上限或遇到业务错误时也要收尾。
    values := make([]int, 0, limit)
    for len(values) 

Numbers 遇到解析失败时,仅交付一个 Err 条目后返回;正常值交付后必须检查 yield 的返回值。消费者不再需要后续值时,生产者应及时退出,不能继续忽略 false 并反复 yield。

Take 先检查 nil 和上限,再创建 Pull 对。所有后续正常返回与业务错误返回都经过 defer stop。错误用 %w 包装,真实项目可继续通过 errors.Is 或 errors.As 识别底层错误,错误文字则不应当成稳定协议。

4. 本地运行:用固定案例读懂预期结果

# 在保存 main.go 的目录中运行示例;此命令由读者自行执行
go run main.go

正常案例应保留0和9;空序列没有值但不报错;空文本在第一项产生解析错误。提前停止案例只要求一个值,因此应得到4,不会继续读取 bad,也不会报告尚未观察到的错误。零上限同样不会解析 bad。最后单独的 nil 数据源会在入口被拒绝。

上述是从代码与固定输入推导的预期行为,不是本机运行日志。不要把“提前停止没有错误”解释为后续所有数据都合法。如果业务要求整批无错,应选择完整消费,再决定是否提交结果。

5. 集成:明确 stop 的资源边界

消费者持有next与stop及生产者清理责任的结构关系
图2:原创静态说明图。消费端持有Pull返回的函数,生产端负责自己的清理;连线表示责任关联,不是时间轴。

如果来源以后改成文件、游标或网络数据,资源在生产者内部打开时,应由生产者自己的 defer 关闭。stop 的意义是让尚未结束的迭代器有机会完成返回,不是让消费者越过所有权直接关闭别人的句柄。

stop 也不等于抢占式超时取消。生产者若阻塞在不响应取消的操作里,不能指望调用 stop 就立即打断一切。涉及外部等待时,应给底层操作设计可取消机制和明确的资源拥有者;本内存示例没有模拟这类承诺。

同一 Pull 对不能从多个 goroutine 同时调用 next 或 stop。若迭代器在 next 或 stop 的过程中 panic,相应调用也会传播该 panic;当前包装只处理正常的业务 error,不用 recover 隐藏程序错误。资源清理中的 panic 同样不能被当成普通耗尽。

6. 验收设计与常见问题

接入项目时应覆盖空源、合法0、错误首项、错误位于已取前缀内、错误位于上限之后、负上限和大于1000的上限。还要观察自定义生产者提前退出时的清理行为。这里提供的是验收设计,没有运行这些检查,也不报告性能数据。

耗尽后还能调用 next 吗?

可以,仍返回零值与 false;调用 stop 后再调用 next 也会保持无有效值。业务代码通常直接离开当前消费作用域,不需要用反复调用来探测状态。

已经耗尽还要执行 defer stop 吗?

可以保留。stop 允许重复调用,也允许在 next 已返回 false 后调用。统一 defer 比在每个分支猜测“是不是已经结束”更容易维护。

nil 切片输入为何不是 nil 迭代器?

Numbers(nil) 返回的是一个存在的函数,只是没有元素可交付;Take(nil, 5) 传入的函数值本身为 nil。两种情况的合同不同,不能用“都是空”混过去。

为什么不直接用 range?

只需顺序遍历时,range 往往更直接。这里保留 Pull 是为了展示调用方主动决定何时取下一项及何时停止的场景;不要为了使用新接口,把普通循环改得更复杂。

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