> 技术文档 > 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” 特性)


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.Seqiter.Seq2 两种 push iterator 类型,以及 iter.Pull 辅助函数,用来在 push 与 pull 迭代器之间相互转换。

本篇文章基于 Ian Lance Taylor 在 GopherCon 2024 的演讲,并结合源码与示例,深入解析该特性,并给出最佳实践与性能考量。

2024年08月20日 Go生态洞察:Range Over Function Types( Go 1.23 新增的 “在函数类型上使用 for/range” 特性)

猫头虎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 元素

另一种模式,返回 nextstop 函数,按需拉取:

// 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.Valuesslices.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 })}

技术扩展

  • 展示迭代器即函数的第一类地位,可动态构造与调用。

📋 知识要点总结

模块 要点 Push 迭代 回调传参,bool 控制终止;适合简单遍历 Pull 迭代 goroutine + channel;返回 next/stop;适合并行/双序列处理 标准迭代器 iter.Seq/Seq2;直接 for/range;高效、无 goroutine 适配器模式 可组合 Filter、Map、FlatMap;惰性求值;支持无限序列 标准库支持 slices、maps 新增 All/Values/Collect 等函数 实战场景 二叉树、文件行遍历、并行比较、数据流式处理

❓ 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


🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🐅🐾🍁🐥

学习 复习 Go生态 ✔ ✔ ✔

粉丝福利


👉 更多信息:有任何疑问或者需要进一步探讨的内容,欢迎点击文末名片获取更多信息。我是猫头虎,期待与您的交流! 🦉💬


联系我与版权声明 📩

  • 联系方式
    • 微信: Libin9iOak
    • 公众号: 猫头虎技术团队
    • 万粉变现经纪人微信: CSDNWF
  • 版权声明
    本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问猫头虎的博客首页。

点击✨⬇️下方名片⬇️✨,加入猫头虎AI编程共创社群。一起探索科技的未来,共同成长。🚀

🔗 猫头虎AI编程共创500人社群 | 🔗 GitHub 代码仓库 | 🔗 Go生态洞察专栏 ✨ 猫头虎精品博文专栏🔗

在这里插入图片描述

在这里插入图片描述