2024年08月20日 Go生态洞察:Range Over Function Types( Go 1.23 新增的 “在函数类型上使用 for/range” 特性)
2024年08月20日 Go生态洞察:Range Over Function Types
🚀 摘要
大家好,我是 猫头虎,本篇文章将深入剖析 Go 1.23 新增的 “在函数类型上使用 for/range” 特性 —— 又称 Range Over Function Types。我们将结合泛型容器、迭代器模式、push 与 pull 两种迭代方式,以及标准库中对切片和映射包的新支持,全面探讨这一特性背后的设计动机、实现原理与最佳实践。
关键词: Go 1.23、函数迭代、迭代器、for/range、泛型容器
✨ 引言
Go 自 1.18 支持泛型以来,为用户自定义容器类型提供了强大能力。然而,如何统一遍历这些容器却一直缺乏语言级别的标准:不同包各自实现 Push、Pull、MapRange、CallersFrames 等接口,各有差异,使用体验并不一致。
在 Go 1.23 中,Go 团队为此引入了 “在函数类型上使用 for/range” 特性,使我们能够对用户自定义容器进行直观、统一的 for/range 遍历。同时,标准库新增了 iter.Seq
、iter.Seq2
两种 push iterator 类型,以及 iter.Pull
辅助函数,用来在 push 与 pull 迭代器之间相互转换。
本篇文章基于 Ian Lance Taylor 在 GopherCon 2024 的演讲,并结合源码与示例,深入解析该特性,并给出最佳实践与性能考量。
猫头虎AI分享:Go生态洞察
- 2024年08月20日 Go生态洞察:Range Over Function Types
-
- 🚀 摘要
- ✨ 引言
- 作者简介
-
- 猫头虎是谁?
- 作者名片 ✍️
- 加入我们AI编程共创团队 🌐
- 加入猫头虎的AI共创编程圈,一起探索编程世界的无限可能! 🚀
- 正文
-
- 1. 💡 为什么要引入 Range Over Function Types?
- 2. 🔄 Push 与 Pull 两种传统遍历方式
-
- 2.1 👍 Push Set 元素
- 2.2 🤲 Pull Set 元素
- 3. ⚙️ 标准化迭代器接口
-
- 3.1 📜 迭代器模式回顾
- 3.2 🔄 for/range 扩展
- 4. 🚀 Go 1.23 的改进
-
- 4.1 ✨ 标准(Push)迭代器类型
- 4.2 🔄 Pull 迭代器辅助
- 5. 🔌 适配器与示例
-
- 5.1 🔧 Filter 适配器
- 5.2 🌳 二叉树示例
- 5.3 📦 切片与映射包新函数
- 5.4 📄 文件行迭代示例
- 5.5 🎯 直接调用 Push 迭代器
- 📋 知识要点总结
- ❓ QA 环节
- ✅ 总结
- 📝 下一篇预告
- 🐅🐾猫头虎建议Go程序员必备技术栈一览表📖:
- 粉丝福利
-
-
- 联系我与版权声明 📩
-
作者简介
猫头虎是谁?
大家好,我是 猫头虎,猫头虎技术团队创始人,也被大家称为猫哥。我目前是COC北京城市开发者社区主理人、COC西安城市开发者社区主理人,以及云原生开发者社区主理人,在多个技术领域如云原生、前端、后端、运维和AI都具备丰富经验。
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用方法、前沿科技资讯、产品评测、产品使用体验,以及产品优缺点分析、横向对比、技术沙龙参会体验等。我的分享聚焦于云服务产品评测、AI产品对比、开发板性能测试和技术报告。
目前,我活跃在CSDN、51CTO、腾讯云、阿里云开发者社区、知乎、微信公众号、视频号、抖音、B站、小红书等平台,全网粉丝已超过30万。我所有平台的IP名称统一为猫头虎或猫头虎技术团队。
我希望通过我的分享,帮助大家更好地掌握和使用各种技术产品,提升开发效率与体验。
作者名片 ✍️
- 博主:猫头虎
- 全网搜索IP关键词:猫头虎
- 作者微信号:Libin9iOak
- 作者公众号:猫头虎技术团队
- 更新日期:2025年07月21日
- 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能!
加入我们AI编程共创团队 🌐
- 猫头虎AI编程共创社群入口:
- 点我进入共创社群矩阵入口
- 点我进入新矩阵备用链接入口
加入猫头虎的AI共创编程圈,一起探索编程世界的无限可能! 🚀
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁
🦄 博客首页——🐅🐾猫头虎的博客🎐
正文
1. 💡 为什么要引入 Range Over Function Types?
Go 1.18 之后,用户可以轻松定义泛型容器,例如:
// Set holds a set of elements.type Set[E comparable] struct { m map[E]struct{}}// New returns a new [Set].func New[E comparable]() *Set[E] { return &Set[E]{m: make(map[E]struct{})}}
为容器添加基本操作:
// Add adds an element to a set.func (s *Set[E]) Add(v E) { s.m[v] = struct{}{}}// Contains reports whether an element is in a set.func (s *Set[E]) Contains(v E) bool { _, ok := s.m[v] return ok}
但要统一遍历,就只能在包内直接对 m
字段做 for/range,或者暴露不统一的 Push/Pull API:
// Union returns the union of two sets.func Union[E comparable](s1, s2 *Set[E]) *Set[E] { r := New[E]() for v := range s1.m { r.Add(v) } for v := range s2.m { r.Add(v) } return r}
这种实现只能在同包中使用,且不同容器需学习不同遍历方式,难以利用统一工具函数。Go 1.23 希望用语言支持和标准库来解决这个痛点,让容器遍历像切片、映射那样,直接 for v := range …
。
2. 🔄 Push 与 Pull 两种传统遍历方式
2.1 👍 Push Set 元素
早期常见做法,在容器上定义接收回调函数的 Push 方法:
func (s *Set[E]) Push(f func(E) bool) { for v := range s.m { if !f(v) { return } }}
使用示例:
func PrintAllElementsPush[E comparable](s *Set[E]) { s.Push(func(v E) bool { fmt.Println(v) return true })}
技术扩展
- 中断机制:Push 通过回调返回 bool 控制提早退出。
- 性能:回调函数调用开销较小,但在热路径会产生额外栈分配,Go 编译器可内联,但要注意闭包捕获。
- 应用场景:适合一次性、无需并行遍历的场景。
2.2 🤲 Pull Set 元素
另一种模式,返回 next
与 stop
函数,按需拉取:
// Pull returns a next function that returns each// element of s with a bool for whether the value// is valid. The stop function should be called// when finished calling the next function.func (s *Set[E]) Pull() (func() (E, bool), func()) { ch := make(chan E) stopCh := make(chan bool) go func() { defer close(ch) for v := range s.m { select { case ch <- v: case <-stopCh: return } } }() next := func() (E, bool) { v, ok := <-ch return v, ok } stop := func() { close(stopCh) } return next, stop}
使用示例:
func PrintAllElementsPull[E comparable](s *Set[E]) { next, stop := s.Pull() defer stop() for v, ok := next(); ok; v, ok = next() { fmt.Println(v) }}
技术扩展
- 并发与内存:Pull 内部启动 goroutine 与双通道,解耦迭代者和生产者,但会有 goroutine 与 channel 的调度开销。
- 提前终止:必须调用 stop 防止 goroutine 泄漏。
3. ⚙️ 标准化迭代器接口
3.1 📜 迭代器模式回顾
迭代器(Iterator)模式源于 CLU 语言,由 Design Patterns 一书推广:
“Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.”
许多语言(C++、Java、Python、Rust)均有原生支持;而 Go 在 1.23 之前,只有内建容器的 for/range,可自定义容器却无统一支持。
3.2 🔄 for/range 扩展
Go 1.23 将 for/range 扩展至支持三种函数类型:
func(yield func() bool)func(yield func(V) bool)func(yield func(K, V) bool)
其中,yield
函数签名决定是否继续迭代。编译器在 for v := range f
时,会生成一个隐式 yield
并传入 f
,实现高效展开。
4. 🚀 Go 1.23 的改进
4.1 ✨ 标准(Push)迭代器类型
新增 iter
包,定义两种函数类型:
package itertype Seq[V any] func(yield func(V) bool)type Seq2[K, V any] func(yield func(K, V) bool)
结合 for/range,可对任意 Seq/Seq2 调用:
// All is an iterator over the elements of s.func (s *Set[E]) All() iter.Seq[E] { return func(yield func(E) bool) { for v := range s.m { if !yield(v) { return } } }}// 使用示例func PrintAllElements[E comparable](s *Set[E]) { for v := range s.All() { fmt.Println(v) }}
技术扩展
- 编译器支持:编译器将 for/range 转译为对 Seq 的调用和闭包构造,无反射,无接口装箱。
- 性能对比:与传统切片 for 相当;与 Push/Pull 相比,无额外 goroutine 或 channel,且不需 stop。
4.2 🔄 Pull 迭代器辅助
iter.Pull
函数可将 Push 迭代器转为 Pull 迭代器:
// EqSeq reports whether two iterators contain the same// elements in the same order.func EqSeq[E comparable](s1, s2 iter.Seq[E]) bool { next1, stop1 := iter.Pull(s1) defer stop1() next2, stop2 := iter.Pull(s2) defer stop2() for { v1, ok1 := next1() v2, ok2 := next2() if !ok1 { return !ok2 } if ok1 != ok2 || v1 != v2 { return false } }}
技术扩展
- 并行/双序列对比:Pull 迭代器特别适合多序列并行处理,应用于归并、对比、Zip 等场景。
- 自动清理:Pull 返回 stop 函数,保证资源及时释放。
5. 🔌 适配器与示例
5.1 🔧 Filter 适配器
借助标准迭代器,可编写通用适配器:
// Filter returns a sequence that contains the elements// of s for which f returns true.func Filter[V any](f func(V) bool, s iter.Seq[V]) iter.Seq[V] { return func(yield func(V) bool) { for v := range s { if f(v) { if !yield(v) { return } } } }}
示例:结合 maps.Values
与 slices.Collect
,提取长度大于等于 n 的字符串:
// LongStrings returns a slice of just the values// in m whose length is n or more.func LongStrings(m map[int]string, n int) []string { isLong := func(s string) bool { return len(s) >= n } return slices.Collect(Filter(isLong, maps.Values(m)))}
技术扩展
- 链式调用:Filter、Map、FlatMap 等适配器可组合,形成数据流式处理。
- 惰性求值:迭代器直到被 Collect 或 for/range 才真正遍历,支持无限序列。
5.2 🌳 二叉树示例
递归实现中序遍历:
// Tree is a binary tree.type Tree[E any] struct { val E left, right *Tree[E]}// All returns an iterator over the values in t.func (t *Tree[E]) All() iter.Seq[E] { return func(yield func(E) bool) { t.push(yield) }}// push pushes all elements to the yield function.func (t *Tree[E]) push(yield func(E) bool) bool { if t == nil { return true } return t.left.push(yield) && yield(t.val) && t.right.push(yield)}
技术扩展
- 递归 vs 显式栈:利用调用栈,无需手动管理堆栈。
- 提前终止:yield 返回 false 即可迅速停止递归。
5.3 📦 切片与映射包新函数
-
slices 包
All([]E) iter.Seq2[int, E]
Values([]E) iter.Seq[E]
Collect(iter.Seq[E]) []E
- …
-
maps 包
All(map[K]V) iter.Seq2[K, V]
Keys(map[K]V) iter.Seq[K]
Values(map[K]V) iter.Seq[V]
Collect(iter.Seq2[K, V]) map[K]V
- …
示例:
for idx, v := range slices.All(mySlice) { fmt.Printf(\"index=%d, value=%v\\n\", idx, v)}
5.4 📄 文件行迭代示例
在大文件或流式场景下,避免一次性分割:
// Lines returns an iterator over lines in data.func Lines(data []byte) iter.Seq[[]byte] { return func(yield func([]byte) bool) { for len(data) > 0 { line, rest, _ := bytes.Cut(data, []byte{\'\\n\'}) if !yield(line) { return } data = rest } }}// 使用示例for line := range Lines(data) { handleLine(line)}
技术扩展
- 零拷贝:无需额外分片,直接
bytes.Cut
,GC 压力更低。- 流式处理:可与
bufio.Reader.ReadSlice
结合,实现大文件行遍历。
5.5 🎯 直接调用 Push 迭代器
func PrintAllElements[E comparable](s *Set[E]) { s.All()(func(v E) bool { fmt.Println(v) return true })}
技术扩展
- 展示迭代器即函数的第一类地位,可动态构造与调用。
📋 知识要点总结
iter.Seq
/Seq2
;直接 for/range;高效、无 goroutine❓ QA 环节
Q1:Push 与标准迭代器哪种性能更优?
- 标准迭代器在编译期将闭包内联,执行时无额外 goroutine 和 channel,通常略胜一筹。
Q2:何时使用 Pull 迭代器?
- 需要多序列并行处理、随机访问、流式消费或提前终止清理资源时,Pull 模式更灵活。
Q3:如何在现有模块中启用此特性?
-
在
go.mod
中将 Go 版本设为1.23
:go mod edit -go=1.23
或在文件头使用
//go:build go1.23
。
✅ 总结
本文由 猫头虎 博主撰写,深度解析了 Go 1.23 “在函数类型上使用 for/range” 特性,涵盖了 Push 与 Pull 两种迭代模式、标准迭代器设计及标准库支持,并通过多种场景示例展现其最佳实践。
本篇文章已收录于「猫头虎的 Go 生态洞察」专栏,详情请点击:https://blog.csdn.net/qq_44866828/category_12492877.html
📝 下一篇预告
在下一篇文章中,我将带大家探索 Go 1.23 中全新的 unique
包:深入剖析其设计理念、实现细节,以及在去重与集合运算中的高效应用。敬请期待!
学会Golang语言,畅玩云原生,走遍大小厂~💐
🐅🐾猫头虎建议Go程序员必备技术栈一览表📖:
☁️🐳
Go语言开发者必备技术栈☸️
:
🐹 GoLang | 🌿 Git | 🐳 Docker | ☸️ Kubernetes | 🔧 CI/CD | ✅ Testing | 💾 SQL/NoSQL | 📡 gRPC | ☁️ Cloud | 📊 Prometheus | 📚 ELK Stack |AI
🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🐅🐾🍁🐥
粉丝福利
👉 更多信息:有任何疑问或者需要进一步探讨的内容,欢迎点击文末名片获取更多信息。我是猫头虎,期待与您的交流! 🦉💬
联系我与版权声明 📩
- 联系方式:
- 微信: Libin9iOak
- 公众号: 猫头虎技术团队
- 万粉变现经纪人微信: CSDNWF
- 版权声明:
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问猫头虎的博客首页。
点击✨⬇️下方名片
⬇️✨,加入猫头虎AI编程共创社群。一起探索科技的未来,共同成长。🚀
🔗 猫头虎AI编程共创500人社群 | 🔗 GitHub 代码仓库 | 🔗 Go生态洞察专栏 ✨ 猫头虎精品博文专栏🔗