本文带大家一起来深入探究一下 Go 1.23 中发布的迭代器特性,这是一篇迟来的文章,再不写这篇文章 Go 1.25 就发布了 :),Go 1.25 预计将于 2025 年 8 月发布。
由于篇幅过长,所以文章拆分成了上下两篇发布,本文为上篇------使用篇。下篇为------原理篇,记得来读。
何为迭代器
维基百科对迭代器的定义如下:
迭代器(英语:iterator),是使用户可在容器对象(container,例如链表或数组)上遍访的对象,设计人员使用此接口无需关心容器对象的内存分配的实现细节。其行为很像数据库技术中的光标(cursor),迭代器最早出现在1974年设计的CLU编程语言中。
在各种语言实现迭代器的方式皆不尽同,有些面向对象语言像Java、C#、Ruby、Python、Delphi都已将迭代器的特性内置语言当中,完美的跟语言集成,我们称之隐式迭代器。但像是C++语言本身就没有迭代器的特色,但STL仍利用模板实现了功能强大的迭代器。STL容器的数据的内存地址可能会重新分配(reallocate),与容器绑定的迭代器仍然可以定位到重新分配后的正确的内存地址。
虽然这段对迭代器的定义比较晦涩,但可以看出,许多主流编程语言都原生支持迭代器。而 Go 也终于在 1.23 中支持了迭代器特性,这是继泛型特性以来 Go 在语法层面上的又一次重大更新。
我们再来看看 Go 官方是如何定义迭代器的,Go 官方在 1.23 版本新增的 iter 包文档 中对于迭代器定义如下:
An iterator is a function that passes successive elements of a sequence to a callback function, conventionally named yield. The function stops either when the sequence is finished or when yield returns false, indicating to stop the iteration early.
翻译如下:
迭代器是一个将序列的连续元素传递给回调函数(通常命名为 yield)的函数。该函数在序列结束或 yield 返回 false(表示提前停止迭代)时停止。
看到这个定义,你可能还不是很理解,不过没关系,你现在只需要记得迭代器是一个函数即可,接下来我们将通过此文彻底掌握 Go 迭代器。
为什么要引入迭代器
在 discussions/56413 中 Go 团队技术负责人 rsc 对 Go 中为什么要引入迭代器做了诠释:
In the standard library alone, we have archive/tar.Reader.Next, bufio.Reader.ReadByte, bufio.Scanner.Scan, container/ring.Ring.Do, database/sql.Rows, expvar.Do, flag.Visit, go/token.FileSet.Iterate, path/filepath.Walk, go/token.FileSet.Iterate, runtime.Frames.Next, and sync.Map.Range, hardly any of which agree on the exact details of iteration. Even the functions that agree on the signature don't always agree about the semantics. For example, most iteration functions that return (T, bool) follow the usual Go convention of having the bool indicate whether the T is valid. In contrast, the bool returned from runtime.Frames.Next indicates whether the next call will return something valid.
大概意思是说,仅标准库中,就包含这么多迭代器函数,并且这些函数接口并未统一,各自为政。甚至有些迭代器即使在函数签名上达成了一致,在语义上却各不相同。这就使得我们学习 Go 代码的成本提高了,而且与 Go 语言面向工程的简洁特点相悖。
于是,这个由 rsc 牵头,社区中争议很大的特性在 Go 1.23 中落地了。
这里举几个例子,你可以先来感受一下 Go 语言在未提供迭代器特性时的现状:
go
// bufio.Reader
r := bufio.NewReader(...)
for {
line, _ , err := r.ReadLine()
if err != nil {
break
}
// do something
}
// bufio.Scanner
scanner := bufio.NewScanner(...)
for scanner.Scan() {
line := scanner.Text()
// do something
}
// database/sql.Rows
rows, _ := db.QueryContext(...)
for rows.Next() {
if err := rows.Scan(...); err != nil {
break
}
// do something
}
可以发现,Go 语言中迭代器函数的设计非常混乱,接口设计各不相同,这些设计可能都是当时的最佳实践,但是接口不统一的事实,确实存在问题,急需一个统一的标准来解决此问题。
所以,其实迭代器在 Go 语言中并不是什么新鲜的东西,它们一直存在,只不过各个迭代器函数实现接口并不统一。这个问题早期也许不明显,但随着 Go 语言标准库功能的增多以及泛型特性的引入,越来越多的泛型集合实现,也都需要设计迭代器接口。因此,语法层面的迭代器特性呼之欲出。
现在,你大概理解什么是迭代器了吗?你可以简单的,将能够作用于 for
循环,并不断产生下一个值的对象,称为迭代器。
迭代器示例
我们已经对迭代器有了初步的认识,为了加深你对 Go 中迭代器的理解,现在我们一起来自己实现一下迭代器。
迭代器模式
Go 是现代语言中的新贵,虽然与主流面向对象语言在设计上有所出入,不过 Go 也支持通过设计模式中的迭代器模式来实现迭代器特性。
示例如下:
go
package main
import "fmt"
type Iterator struct {
data []int
index int
}
func NewIterator(data []int) *Iterator {
return &Iterator{data: data, index: 0}
}
func (it *Iterator) HasNext() bool {
return it.index < len(it.data)
}
func (it *Iterator) Next() int {
if !it.HasNext() {
panic("Stop iteration")
}
value := it.data[it.index]
it.index++
return value
}
func main() {
it := NewIterator([]int{0, 1, 2, 3, 4})
for it.HasNext() {
fmt.Println(it.Next())
}
}
这是 Go 语言实现的简化版本的迭代器模式,NewIterator
创建并返回一个迭代器对象,it.HasNext()
返回迭代器中是否还有下一个值,刚好可以作用于 for
循环对其进行遍历,it.Next()
返回迭代器中的下一个值。
回调函数风格
此外,因为 Go 语言支持高阶函数,即函数可以作为另一个函数的参数,所以我们还可以通过回调函数的方式,来实现迭代器。
示例如下:
go
package main
import (
"container/ring"
"fmt"
)
func main() {
// 循环链表
r := ring.New(5)
// 初始化链表
for i := 0; i < r.Len(); i++ {
r.Value = i // 为当前节点赋值
r = r.Next() // 移动到下一个节点
}
// 迭代器
r.Do(func(v any) {
fmt.Println(v)
})
}
这里引入了标准库中的 container/ring
包,这个包实现了循环链表,它定义了 *Ring.Do
方法来对链表 Ring
对象进行遍历操作。
Do
方法按正向顺序对链表中的每个元素调用函数 f
,其实现如下:
go
// Do calls function f on each element of the ring, in forward order.
// The behavior of Do is undefined if f changes *r.
func (r *Ring) Do(f func(any)) {
if r != nil {
f(r.Value)
for p := r.Next(); p != r; p = p.next {
f(p.Value)
}
}
}
可以看到,Do
方法内部有一个 for
循环,不停的调用 r.Next()
来获取链表中的下一个值,然后调用回调函数 f
并将下一个值传递给它。
Go 风格迭代器
以上两种方式实现的迭代器,在其他主流编程语言中也很容易复刻。现在我们再来使用 channel
数据结构实现一种 Go 语言独有风格的迭代器。
示例如下:
go
package main
import "fmt"
func generator(n int) <-chan int {
ch := make(chan int)
go func() {
for i := 0; i < n; i++ {
ch <- i
}
close(ch)
}()
return ch
}
func main() {
for n := range generator(5) {
fmt.Println(n)
}
}
这里我们定义了一个生成器函数 generator
,它返回一个只读的 <-chan int
。而 channel
类型恰好能够作用于 for-range
循环,生成器 generator
不断产生下一个值,for-range
循环就能接收到,这样我们就实现了 Go 风格的迭代器。
虽然这个版本的迭代器非常具有 Go 风格,但因为使用了 channel
所以性能较差,不建议作为实现迭代器的首选方案。
有了前面的铺垫,接下来我们就要真正进入到 Go 语言官方推出的迭代器的学习了。
何时引入迭代器
Go 引入迭代器的过程可谓一波多折,在 2020 年 8 月的 issues/40605 中就有人为 Go 2 提出了迭代器提案,之后又经历了在 issues/43557、discussions/54245、discussions/56413、issues/61405、issues/61897 中多次讨论。终于,迭代器特性以实验性质被加入到 Go 1.22 版本中,通过 GOEXPERIMENT=rangefunc
编译参数可以在 Go 中启用 range-over-function 迭代器特性。此外,rsc 还在 issues/61898 中提出了 golang.org/x/exp/xiter
包的提案,用于适配迭代器,不过这个提案最终被撤销了。
2024 年 8 月,随着 Go 1.23 版本的发布,Go 迭代器也终于正式落地了。同时引入了 iter 包,来为用户自定义迭代器提供便利。
这便是 Go 引入迭代器的主要时间脉络。
改变
Go 语言发布之初,for-range
循环就能够支持遍历 array
、slice
、string
、map
、channel
几种内置类型。
在 Go 1.22 中,for-range
新增了对整数类型值(integer value
)的支持,比如 for i := range 10 {...}
。
而在 Go 1.23 中,Go 引入了迭代器特性,for-range
又新增了对如下三种特定函数类型的支持:
go
// 无返回值迭代器,函数 f 签名为:func(func() bool)
for range f {...}
// 返回一个值迭代器,函数 f 签名为:func(func(V) bool)
for x := range f { ... }
// 返回两个值迭代器,函数 f 签名为:func(func(K, V) bool)
for x, y := range f { ... }
现在,for-range
支持遍历的所有类型如下:
go
Range expression 1st value 2nd value
array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E
integer value n integer type, or untyped int value i see below
function, 0 values f func(func() bool)
function, 1 value f func(func(V) bool) value v V
function, 2 values f func(func(K, V) bool) key k K v V
至此,Go 语言 for-range
语句支持的可迭代对象类型完整拼图已经形成,真正实现了"万物皆可遍历"的能力。
迭代器使用示例
基于以上的讲解,你可能对 Go 迭代器还是有些不明所以,那么现在,咱们一起从代码层面,切身感受一下迭代器的存在。我将通过几个示例代码,带你体验迭代器的用法。
迭代器实现
最简单的迭代器
我们先来实现一个最简单的迭代器。
示例如下:
go
package main
import (
"fmt"
)
func iterator(yield func() bool) {
for i := 0; i < 5; i++ {
if !yield() {
return
}
}
}
func main() {
i := 0
for range iterator {
fmt.Printf("i=%d\n", i)
i++
}
}
iterator
函数就是一个迭代器,它接收一个 yield
函数作为参数,并且函数签名符合 func(func() bool)
类型,那么 iterator
就能够作用于 for-range
循环。
在 iterator
函数内部,我们启动了一个 for
循环,这个循环将会迭代 5 次。并且在 for
循环代码块内部,每次迭代都会调用 yield()
函数,并判断其返回值,如果返回 false
,则直接使用 return
提前终止循环。
执行示例代码,得到输出如下:
bash
$ go run main.go
i=0
i=1
i=2
i=3
i=4
这个示例看起来有点傻,但这确实已经是 Go 中最简单的迭代器实现了。
你也许比较疑惑,这个示例代码的执行逻辑是什么?咱们暂且不去深究原理,挖一个坑留在这里,稍后再来填上。
现在,你只需要知道这段代码大概按照如下流程来执行:
- 首先,符合函数签名
func(func() bool)
的函数,都可以作为迭代器,所以iterator
是一个迭代器,可以应用于for-range
循环。 - 接着,当
for-range
开始迭代时,迭代器函数iterator
会被调用,并执行其内部代码。这里你需要注意,iterator
函数只会被调用一次。 - 现在重点来了,
iterator
函数接收一个yield
函数作为参数,那么这个yield
函数是哪里来的呢?其实它是 Go 编译器帮我们自动生成的函数,并自动作为参数传递给iterator
。yield
函数内部包含了for-range
循环体中的代码,你可以简单的将yield
函数理解为如下伪代码:
go
func yield() bool {
fmt.Printf("i=%d\n", i)
i++
return true
}
- 在
iterator
函数内部,有一个for
循环,将执行 5 次。每一次循环都会调用yield
函数,并根据其返回值决定是否停止迭代。 for
循环一旦结束,iterator
函数便执行完成,即for-range
迭代完成。
现在,是不是对迭代器的认知更清晰了一些呢?
控制迭代次数
刚刚的迭代器实现过于简单,甚至都无法从外部控制迭代次数,为了控制迭代次数,我们可以给 iterator
再嵌套一层函数。
示例如下:
go
package main
import (
"fmt"
)
func iterator(n int) func(yield func() bool) {
return func(yield func() bool) {
for i := 0; i < n; i++ {
if !yield() {
return
}
}
}
}
func main() {
i := 0
for range iterator(3) {
fmt.Printf("i=%d\n", i)
i++
}
}
这里为 iterator
又嵌套了一层函数,这样就可以通过参数的形式控制迭代次数了。
执行示例代码,得到输出如下:
bash
$ go run main.go
i=0
i=1
i=2
值得注意的是,现在 for-range
遍历迭代器时,不再是直接使用函数名称 for range iterator
的形式进行迭代,而是要调用 iterator
函数以 for range iterator(3)
的形式来迭代。所以,实际上 iterator(3)
的返回值,才是真正的迭代器函数。
这个示例代码的套路是不是有点熟悉?用 Go 写过 Web 程序的读者应该能够体会得到,比如我们在用 Gin 框架开发项目时,编写的中间件程序就经常使用这种套路。如开源项目 miniblog 中的 AuthnMiddleware 中间件实现。
输出一个值
我们已经展示了两个迭代器示例,但是,它们都不产生值,在使用 for-range
进行迭代时,真的就是纯迭代,for-range
接收不到任何循环变量,所以感觉也没啥大用。
那么我们再来实现一个能够在每轮迭代时输出一个值的迭代器。
示例如下:
go
package main
import (
"fmt"
)
func iterator(n int) func(yield func(v int) bool) {
return func(yield func(v int) bool) {
for i := 0; i < n; i++ {
if !yield(i) {
return
}
}
}
}
func main() {
i := 0
for v := range iterator(10) {
if i >= 5 {
break
}
fmt.Printf("%d => %d\n", i, v)
i++
}
}
这个版本的迭代器函数签名已经变了,不再是 func(func() bool)
,而变成了 func(func(V) bool)
。即 yield
函数支持接收一个参数 v int
。
执行示例代码,得到输出如下:
bash
$ go run main.go
0 => 0
1 => 1
2 => 2
3 => 3
4 => 4
现在这个示例程序终于看起来有点意义了,像那么回事了。
输出两个值
讲到这里,想必你已经猜到接下来我们要实现的迭代器功能了。
没错,for-range
在迭代容器类型数据结构时,是支持接收两个循环变量的。比如迭代 slice
时会产生 index
和 value
两个循环变量,迭代 map
时会产生 key
、value
两个循环变量。
我们先来实现支持迭代 slice
类型的迭代器。
示例如下:
go
package main
import (
"fmt"
)
func iterator(slice []int) func(yield func(i, v int) bool) {
return func(yield func(i int, v int) bool) {
for i, v := range slice {
if !yield(i, v) {
return
}
}
}
}
func main() {
s := []int{0, 1, 2, 3, 4}
for i, v := range iterator(s) {
if i == 2 {
continue
}
fmt.Printf("%d => %d\n", i, v)
}
}
这个迭代器函数签名为 func(func(K, V) bool)
,即支持输出两个值,分别是 slice
对象索引(i
)和元素(v
)。
执行示例代码,得到输出如下:
bash
$ go run main.go
0 => 0
1 => 1
3 => 3
4 => 4
根据输出,可以发现我们正确的得到了 index
和 value
。并且,continue
语句也可以正常应用于迭代器。
迭代 map
最后,我们再来来实现一下支持迭代 map
类型的迭代器。
示例如下:
go
package main
import (
"fmt"
)
func iterator(m map[string]int) func(yield func(k string, v int) bool) {
return func(yield func(k string, v int) bool) {
for k, v := range m {
if !yield(k, v) {
return
}
}
}
}
func main() {
m := map[string]int{
"a": 0,
"b": 1,
"c": 2,
}
for k, v := range iterator(m) {
fmt.Printf("%s: %d\n", k, v)
}
}
可以发现,这里其实并没有什么新的内容,甚至迭代器函数签名都没变 func(func(K, V) bool)
,只不过 K
、V
的类型变了。只需简单改造,我们就已经实现了支持迭代 map
类型的迭代器。
执行示例代码,得到输出如下:
bash
$ go run main.go
b: 1
c: 2
a: 0
本小结,我们一起实现了几个迭代器。这些迭代器看起来有点用,但好像也没那么有用。而且,仅从示例程序来看,我们好像把事情变得更加复杂了。
如你所见,目前来看的确如此。比如迭代 slice
对象,只需要用 for i, v := range slice
即可,为什么还需要构造一个迭代器呢?
咱们接着往下看,你会得到答案。
泛型版本
初次接触 Go 迭代器时,你可能也和我一样,有点云里雾里,Get 不到其用意。
现在,我们使用泛型来重新实现一遍这几个迭代器函数,相信你会对迭代器的用途有更深入的理解。
输出零个值
以下是泛型版本最简单的迭代器实现。
示例如下:
go
package main
import (
"fmt"
)
type Seq0 func(yield func() bool)
func iter0[Slice ~[]E, E any](s Slice) Seq0 {
return func(yield func() bool) {
for range s {
if !yield() {
return
}
}
}
}
func main() {
s1 := []int{1, 2, 3}
i := 0
for range iter0(s1) {
fmt.Printf("i=%d\n", i)
i++
}
fmt.Println("--------------")
s2 := []string{"a", "b", "c"}
i = 0
for range iter0(s2) {
fmt.Printf("i=%d\n", i)
i++
}
}
为了让代码可读性更好一些,我将迭代器函数签名定义为 Seq0
类型,命名中的 0 表示迭代器输出零个值。
为了体现泛型的作用,这里还分别演示了迭代 int
和 string
两种类型的 slice
对象 s1
和 s2
。
执行示例代码,得到输出如下:
bash
$ go run main.go
i=0
i=1
i=2
--------------
i=0
i=1
i=2
输出一个值
同样是迭代 slice
对象,输出一个值的迭代器实现如下:
go
package main
import (
"fmt"
)
type Seq1[V any] func(yield func(V) bool)
func iter1[Slice ~[]E, E any](s Slice) Seq1[E] {
return func(yield func(E) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
func main() {
s1 := []int{1, 2, 3}
for v := range iter1(s1) {
fmt.Printf("v=%d\n", v)
}
fmt.Println("--------------")
s2 := []string{"a", "b", "c"}
for v := range iter1(s2) {
fmt.Printf("v=%s\n", v)
}
}
执行示例代码,得到输出如下:
bash
$ go run main.go
v=1
v=2
v=3
--------------
v=a
v=b
v=c
输出两个值
进一步,输出两个值的迭代器实现如下:
go
package main
import (
"fmt"
)
type Seq2[K, V any] func(yield func(K, V) bool)
func iter2[Slice ~[]E, E any](s Slice) Seq2[int, E] {
return func(yield func(int, E) bool) {
for i, v := range s {
if !yield(i, v) {
return
}
}
}
}
func main() {
s1 := []int{1, 2, 3}
for i, v := range iter2(s1) {
fmt.Printf("%d=%d\n", i, v)
}
fmt.Println("--------------")
s2 := []string{"a", "b", "c"}
for i, v := range iter2(s2) {
fmt.Printf("%d=%s\n", i, v)
}
}
执行示例代码,得到输出如下:
bash
$ go run main.go
0=1
1=2
2=3
--------------
0=a
1=b
2=c
看到这几个泛型版本迭代器的实现,是不是觉得 Go 的迭代器设计确实有点用。针对某种数据结构,我们只需要编写一个迭代器函数,就能迭代此数据结构类型的所有对象。
现在,你知道如何实现泛型版本的 map
迭代器了吗?这个实现就留给你自行去完成了。
本小结最后提醒一下,其实迭代器中的 yield
并非关键字,你可以随意命名,这是只是 Go 官方推荐的约定俗成的名字。
iter 包
前文中,我提到过,Go 随着 1.23 迭代器的发布,新增了 iter 包,而新的 iter
包为用户操作自定义迭代器提供了基础定义。
如下这两个函数类型,就是 iter
包提供的:
go
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
也就是说,其实我们在前文中实现的迭代器,有更便捷的写法。我们无需自己定义 Seq
和 Seq2
类型,iter
包已经为我们定义好了。我们只需要拿过来用就行了。
示例如下:
go
package main
import "iter"
func iter1[Slice ~[]E, E any](s Slice) iter.Seq[E] {
return func(yield func(E) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
func iter2[Slice ~[]E, E any](s Slice) iter.Seq2[int, E] {
return func(yield func(int, E) bool) {
for i, v := range s {
if !yield(i, v) {
return
}
}
}
}
此外,iter
包还提供了两个函数 Pull
和 Pull2
,这两个函数签名如下:
go
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())
func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func())
现在我们还无需知道它们是干什么的,稍后会有专门的小节讲解。
Go 1.23 除了新增 iter
包,也对原有的 slices 包和 maps 进行了增强,为二者新增了很多函数方便与迭代器一起工作。
slices 包
slices 包添加了如下几个与迭代器一起工作的函数:
- All 返回一个遍历切片索引和值的迭代器。
- Values 返回一个遍历切片元素的迭代器。
- Backward 返回一个反向遍历切片的迭代器(从末尾向开头遍历)。
- Collect 将迭代器中的值收集到一个新切片中。
- AppendSeq 将迭代器中的值追加到现有切片中。
- Sorted 将迭代器中的值收集到新切片,并排序该切片。
- SortedFunc 功能同
Sorted
,但支持自定义比较函数。 - SortedStableFunc 功能同
SortedFunc
,但使用稳定排序算法(保持相等元素的原始顺序)。 - Chunk 返回一个遍历切片中连续子切片 的迭代器,每个子切片最多包含
n
个元素。
源码如下:
go
package slices
import (
"cmp"
"iter"
)
// All returns an iterator over index-value pairs in the slice
// in the usual order.
func All[Slice ~[]E, E any](s Slice) iter.Seq2[int, E] {
return func(yield func(int, E) bool) {
for i, v := range s {
if !yield(i, v) {
return
}
}
}
}
// Backward returns an iterator over index-value pairs in the slice,
// traversing it backward with descending indices.
func Backward[Slice ~[]E, E any](s Slice) iter.Seq2[int, E] {
return func(yield func(int, E) bool) {
for i := len(s) - 1; i >= 0; i-- {
if !yield(i, s[i]) {
return
}
}
}
}
// Values returns an iterator that yields the slice elements in order.
func Values[Slice ~[]E, E any](s Slice) iter.Seq[E] {
return func(yield func(E) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
...
可以发现,这里的 All
函数其实就是我们在前文中实现的 iter2
迭代器,Values
函数就是 iter1
迭代器。
这里只粘贴了部分源码,更多实现,就靠你自己去研究了。你还可以查看 pkg.go.dev/slices@go1.... 文档,文档中有每一个迭代器使用示例。
maps 包
maps 包添加了如下几个与迭代器一起工作的函数:
- All 返回一个遍历映射中键值对的迭代器。
- Keys 返回一个遍历映射中键的迭代器。
- Values 返回一个遍历映射中值的迭代器。
- Insert 将迭代器中的键值对添加到现有 map 中。
- Collect 将迭代器中的键值对收集到一个新的 map 中并返回。
源码如下:
go
package maps
import "iter"
// All returns an iterator over key-value pairs from m.
// The iteration order is not specified and is not guaranteed
// to be the same from one call to the next.
func All[Map ~map[K]V, K comparable, V any](m Map) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for k, v := range m {
if !yield(k, v) {
return
}
}
}
}
// Keys returns an iterator over keys in m.
// The iteration order is not specified and is not guaranteed
// to be the same from one call to the next.
func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K] {
return func(yield func(K) bool) {
for k := range m {
if !yield(k) {
return
}
}
}
}
// Values returns an iterator over values in m.
// The iteration order is not specified and is not guaranteed
// to be the same from one call to the next.
func Values[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[V] {
return func(yield func(V) bool) {
for _, v := range m {
if !yield(v) {
return
}
}
}
}
...
这里的 All
函数就是我在前文中让你自行去实现的能够支持迭代 map
类型的迭代器。
对于 maps
包更深入的学习,你还可以查看 pkg.go.dev/maps@go1.23... 文档,文档中有每一个迭代器使用示例。
现在,你对 Go 迭代器的理解是否又深入了一层呢?
有如下示例:
go
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
s := []string{"a", "b", "c"}
for i, v := range slices.All(s) {
fmt.Printf("%d => %s\n", i, v)
}
m := map[string]int{
"a": 0,
"b": 1,
"c": 2,
}
for k, v := range maps.All(m) {
fmt.Printf("%s: %d\n", k, v)
}
}
看到这段代码,是不是感觉对迭代器的作用更加直观了。无论是迭代 slice
还是 map
类型,我们都只需要调用 All
函数,即可完成迭代。
Go 迭代器统一了这两种容器类型的迭代 API,后续如果再有容器类型需要迭代,都可以像这样实现一个 All
函数。这降就低了我们开发者学习不同数据结构的迭代 API 的心智负担。这也有助于形成共识,对用户培养相同的习惯,以后在对 Go 中任意类型的迭代,就成了一个很平常的操作,所有接口都符合直觉。
理想很丰满,现实有点骨感,让我们一起期待那一天的到来。
未完待续。
下篇:《彻底掌握 Go 1.23 中的迭代器------原理篇》
联系我
- 公众号:Go编程世界
- 微信:jianghushinian
- 邮箱:jianghushinian007@outlook.com
- 博客:jianghushinian.cn
- GitHub:github.com/jianghushin...