Go 1.23 range-over-func特性概述
Go 1.23正式引入了range-over-func语法,允许在for-range循环中直接遍历函数类型的值。这一特性为Go语言自定义集合遍历提供了标准化的迭代器协议,解决了长期以来Go缺乏统一迭代器抽象的问题。
range-over-func支持三种函数签名作为range目标:
// 1. 单值yield函数:func(yield func(V) bool)
// 2. 双值yield函数:func(yield func(K, V) bool)
// 3. 无返回值push函数:func(yield func() bool)
yield函数返回bool表示调用方是否希望继续迭代,返回false时迭代器提前终止。这一设计使得迭代器可以按需计算,无需预先物化整个集合。
自定义集合迭代器实现
以一个过滤切片为例,实现一个只返回满足条件元素的迭代器:
package main
import "fmt"
// FilterFunc 定义过滤条件
type FilterFunc[T any] func(T) bool
// Filter 返回一个迭代器函数,只yield满足条件的元素
func Filter[T any](slice []T, f FilterFunc[T]) func(yield func(T) bool) {
return func(yield func(T) bool) {
for _, v := range slice {
if f(v) {
if !yield(v) {
return // 调用方break,提前终止
}
}
}
}
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 使用range-over-func遍历偶数
for v := range Filter(nums, func(n int) bool {
return n%2 == 0
}) {
fmt.Println(v) // 输出: 2, 4, 6, 8, 10
}
}
二叉树前序遍历迭代器实现
利用range-over-func可以优雅地实现树结构的延迟遍历,无需预先将节点收集到切片中:
package main
import "fmt"
type TreeNode[T any] struct {
Value T
Left *TreeNode[T]
Right *TreeNode[T]
}
// PreOrder 前序遍历迭代器
func (n *TreeNode[T]) PreOrder() func(yield func(T) bool) {
return func(yield func(T) bool) {
var traverse func(node *TreeNode[T]) bool
traverse = func(node *TreeNode[T]) bool {
if node == nil {
return true // 继续遍历
}
if !yield(node.Value) {
return false // 调用方break
}
if !traverse(node.Left) {
return false
}
return traverse(node.Right)
}
traverse(n)
}
}
func main() {
root := &TreeNode[int]{
Value: 1,
Left: &TreeNode[int]{Value: 2, Left: &TreeNode[int]{Value: 4}},
Right: &TreeNode[int]{Value: 3, Right: &TreeNode[int]{Value: 5}},
}
// 遍历二叉树,找到第一个大于3的值就停止
for v := range root.PreOrder() {
fmt.Printf("visiting %d\n", v)
if v > 3 {
fmt.Printf("found: %d, stopping\n", v)
break // yield返回false,迭代器立即终止
}
}
// 输出:
// visiting 1
// visiting 2
// visiting 4
// found: 4, stopping
}
Map键值对迭代器与双值yield
当需要同时返回键和值时,使用双值yield函数签名func(yield func(K, V) bool):
package main
import (
"fmt"
"sort"
)
// OrderedMap 保持插入顺序的Map
type OrderedMap[K comparable, V any] struct {
keys []K
data map[K]V
}
func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
return &OrderedMap[K, V]{data: make(map[K]V)}
}
func (m *OrderedMap[K, V]) Set(key K, value V) {
if _, exists := m.data[key]; !exists {
m.keys = append(m.keys, key)
}
m.data[key] = value
}
// All 返回双值迭代器,按插入顺序遍历
func (m *OrderedMap[K, V]) All() func(yield func(K, V) bool) {
return func(yield func(K, V) bool) {
for _, k := range m.keys {
if !yield(k, m.data[k]) {
return
}
}
}
}
// SortedKeys 返回按键排序的迭代器(需K满足cmp.Ordered约束)
func (m *OrderedMap[K, V]) SortedKeys() func(yield func(K, V) bool) {
// 仅当K实现cmp.Ordered时可用
// 此处为示意,实际需更细粒度的类型约束
}
func main() {
m := NewOrderedMap[string, int]()
m.Set("banana", 3)
m.Set("apple", 1)
m.Set("cherry", 2)
// 按插入顺序遍历
for k, v := range m.All() {
fmt.Printf("%s: %d\n", k, v)
}
// 输出:
// banana: 3
// apple: 1
// cherry: 2
}
迭代器组合与管道模式
迭代器函数可以嵌套组合,形成类似函数式编程的管道操作链。每个操作接收一个迭代器,返回一个新的迭代器:
package main
import "fmt"
// Seq 泛型迭代器类型
type Seq[V any] func(yield func(V) bool)
// Map 对每个元素应用转换函数
func Map[V, W any](seq Seq[V], f func(V) W) Seq[W] {
return func(yield func(W) bool) {
for v := range seq {
if !yield(f(v)) {
return
}
}
}
}
// Filter 过滤元素
func Filter[V any](seq Seq[V], f func(V) bool) Seq[V] {
return func(yield func(V) bool) {
for v := range seq {
if f(v) && !yield(v) {
return
}
}
}
}
// Take 取前N个元素
func Take[V any](seq Seq[V], n int) Seq[V] {
return func(yield func(V) bool) {
count := 0
for v := range seq {
if count >= n {
return
}
if !yield(v) {
return
}
count++
}
}
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 管道:过滤偶数 -> 平方 -> 取前3个
seq := Take(
Map(
Filter(
func(yield func(int) bool) {
for _, v := range nums {
if !yield(v) {
return
}
}
},
func(n int) bool { return n%2 == 0 },
),
func(n int) int { return n * n },
),
3,
)
for v := range seq {
fmt.Println(v) // 输出: 4, 16, 36
}
}
iter标准库包与Pull迭代器模式
Go 1.23的标准库iter包提供了iter.Seq[V]和iter.Seq2[K, V]类型别名,以及Pull模式转换函数iter.Pull,将Push模式(yield回调)转换为Pull模式(next/stop函数对):
package main
import (
"fmt"
"iter"
)
func main() {
// 创建一个迭代器
seq := func(yield func(int) bool) {
for i := 1; i <= 10; i++ {
if !yield(i) {
return
}
}
}
// 转换为Pull模式
next, stop := iter.Pull(seq)
defer stop() // 确保资源释放
// 手动迭代
for {
v, ok := next()
if !ok {
break
}
if v%3 == 0 {
fmt.Printf("found multiple of 3: %d\n", v)
continue
}
}
// 输出:
// found multiple of 3: 3
// found multiple of 3: 6
// found multiple of 3: 9
}
Pull模式适用于无法使用range-over-func的场景,如需要在迭代过程中执行复杂控制流(跳过多个元素、条件性回溯等)。Pull模式产生的迭代函数需要在不再使用时调用stop()释放资源,通常通过defer保证。
性能考量与goroutine泄漏防护
range-over-func的Push模式在单线程内同步执行yield回调,不涉及goroutine创建,性能与手动for循环基本持平。Pull模式通过iter.Pull转换时,内部使用goroutine和channel实现,存在goroutine生命周期管理开销:
// Pull模式内部实现简化示意
func Pull[V any](pushSeq func(yield func(V) bool)) (func() (V, bool), func()) {
ch := make(chan V)
done := make(chan struct{})
go func() {
pushSeq(func(v V) bool {
select {
case ch <- v:
return true
case <-done:
return false
}
})
close(ch)
}()
next := func() (V, bool) {
v, ok := <-ch
return v, ok
}
stop := func() {
close(done)
}
return next, stop
}
如果Pull模式中忘记调用stop(),且迭代器内部有未完成的yield调用,goroutine将阻塞在channel发送上导致泄漏。务必通过defer stop()或在明确终止路径上调用stop()。Push模式不存在此问题,应优先使用range-over-func的Push模式。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-fan-xing-die-dai-qi-she-ji-rangeoverfunc-ji-zhi/