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

Go iter.Seq2 怎样在 range 中接收键值

来源:17golang原创

时间:2026-11-14 11:08:06 349浏览 收藏

如果一个函数返回 iter.Seq2[K, V],最直接的消费方式不是手动调用回调,而是把它放到 range 右侧:

package main

import "fmt"

// entries 通过 yield 依次提供键和值;返回 false 时停止继续提供。
func entries() func(func(string, int) bool) {
	return func(yield func(string, int) bool) {
		for name, score := range map[string]int{"go": 2, "rust": 1} {
			// 把一对值交给消费者,false 表示消费者已经结束。
			if !yield(name, score) {
				return
			}
		}
	}
}

func main() {
	// 双值迭代器的两个位置分别绑定到 key 和 value。
	for key, value := range entries() {
		fmt.Println(key, value)
	}
}

在标准库语义里,这个函数类型就是 iter.Seq2[string, int] 的展开形式。K、V 是类型参数位置,不要求调用方使用 key、value 这两个名字;真正重要的是两个值的顺序。

先把 Seq2 看成“成对产生值”的函数

iter.Seq2[K, V] 的定义是 func(yield func(K, V) bool)。它通常表示键值对,但第二个位置也可以是错误、状态或其他与第一个值成对的数据。生成方每次调用 yield(k, v),range 循环体就获得一组对应的两个变量。

iter.Seq2 类型参数、yield 回调和 range 双值变量的静态结构说明图
图1:Seq2 的静态结构说明图,展示 K/V、yield 和 range 两个接收位置的关系,不是运行截图。

因此,下面三种写法的含义不同:

// 读取一对完整值。
for key, value := range seq2 {
	fmt.Println(key, value)
}

// 只关心第一个值,显式丢弃第二个值。
for key, _ := range seq2 {
	fmt.Println(key)
}

// 两个值都不需要时,不要误以为 seq2 只会调用一次。
for range seq2 {
	break
}

变量名不会改变迭代器的语义,位置才会。把 value 和 key 交换,代码仍可能编译,但业务含义会悄悄反过来。

range 如何把两个 yield 参数交给循环体

Go 规范把函数形式的 range 分成零值、单值和双值三种签名。对于 func(yield func(K, V) bool),最多可以声明两个迭代变量:第一个接收 K,第二个接收 V。这也是 iter.Seq2 能够自然接入 range 的原因。

真正需要留意的是停止协议。每轮循环体完成后,range 机制会让合成的 yield 返回 true,迭代器才可以继续提供下一对值;如果循环体执行 break,yield 会返回 false,生成方必须立即返回,不能再次调用它。

iter.Seq2 的 yield true 继续与 break 后 false 停止边界说明图
图2:yield 继续与停止边界的静态关系图,突出 break 后不能继续调用 yield。

自定义迭代器时,判断 yield 的返回值是必要的资源边界。下面的写法会让消费者可以提前结束:

// FindFirst 在找到目标后停止生产,避免继续读取后续数据。
func FindFirst(seq iter.Seq2[string, int], want int) (string, bool) {
	var found string
	for key, value := range seq {
		if value == want {
			found = key
			// break 会让生成方收到 false,并退出当前迭代。
			break
		}
	}
	return found, found != ""
}

如果迭代器背后连接文件、网络流或锁,生成方应在自己的函数返回路径中完成清理;不要把“range 已经 break”误解成可以继续向 yield 写入。

何时改用 Pull2,而不是继续 range

range 适合顺序扫描和在循环体中自然使用 break、continue。如果调用方需要把“取下一个键值对”拆成单独的条件分支,标准库提供 iter.Pull2,返回 next 与 stop 两个函数。

// readOne 演示逐个拉取 Seq2,并在提前结束时释放迭代器。
func readOne(seq iter.Seq2[string, int]) {
	next, stop := iter.Pull2(seq)
	defer stop() // 未读完时也要通知生产方结束。

	key, value, ok := next()
	if !ok {
		return
	}
	fmt.Println(key, value)
}

next 返回的是键、值和有效标志;耗尽时两个值是各自类型的零值,标志为 false。Pull2 不适合多个 goroutine 同时调用 next 或 stop,并发消费应在更高层做明确的同步设计。

几个容易写错的边界

  • 不要把 Seq2 当成 map。它只承诺按生成方提供的顺序产生值,是否可重复遍历、是否有序,要看具体迭代器的约定。
  • 不要忽略单次迭代说明。读取文件或流的迭代器可能只能消费一次,第二次 range 不一定还能得到数据。
  • 不要把两个值当成固定业务命名。Seq2 的第二项常常是 value,也可能是 error;应以返回该迭代器的 API 文档为准。
  • 不要在生成方忽略 false。继续调用已经停止的 yield 会破坏 range 的停止协议。

记住一句话:Seq2 负责按对产生两个值,range 负责把两个位置交给循环变量,yield 的布尔结果负责告诉生产方是否还能继续。需要显式拉取时再换成 Pull2,并用 defer stop() 收尾。

相关问题

iter.Seq2 一定代表 map 的键值吗?

不一定。官方文档说它最常见于键值对,但两个类型位置也可以表达记录与错误、索引与元素等成对数据。

range 提前 break 后迭代器还会继续吗?

正常的 range-over-function 语义下,合成的 yield 会返回 false,迭代器应停止调用 yield 并结束自己的函数。

什么时候使用 Pull2?

当业务需要逐次拉取、把读取放进不同条件分支,或需要显式控制停止时使用;若只是顺序遍历,range 更简洁。

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