Go 1.24迭代器進階:range over func深度實戰 2026
编程语言
Go 1.24迭代器進階:range over func深度實戰
Go 1.22引入了range over int,Go 1.23引入了range over func——Go終於有了「一等公民」的迭代器。但很多Gopher還停留在for i, v := range slice的階段,對range over func的理解僅限於標準庫的slices和maps包。
到了Go 1.24,迭代器生態已經成熟:泛型迭代器、迭代器組合、懶求值管道……這些模式可以讓你的Go程式碼更優雅、更高效、更Go-idiomatic。本文從5個核心模式出發,帶你徹底掌握Go迭代器。
核心概念速覽
| 概念 | 說明 | 簽名 |
|---|---|---|
| 迭代器函式(Iterator Function) | 可被range遍歷的函式型別 | func(yield func(V) bool) |
| 帶索引迭代器 | 同時回傳索引和值 | func(yield func(int, V) bool) |
| Pull迭代器 | 手動拉取下一個值 | func() (V, bool) |
| Push迭代器 | 推送值給yield回呼 | func(yield func(V) bool) |
| 迭代器組合 | 多個迭代器串聯/並聯 | func(...Iter) Iter |
| 懶求值 | 延遲到實際消費時才計算 | 迭代器天然懶求值 |
Go迭代器的5大痛點
- 自訂集合遍歷繁瑣:樹、圖、鏈結串列等結構需要手寫遍歷回呼,程式碼醜陋
- 管道式資料處理缺失:無法像Python生成器那樣鏈式filter/map
- 泛型迭代器型別複雜:
func(yield func(V) bool)的簽名讓初學者頭痛 - 迭代器與goroutine互動:併發迭代器的正確使用姿勢不清晰
- Pull vs Push選擇困難:兩種迭代器模式何時用哪種不明確
模式一:range over func基礎
Go 1.23引入的三種迭代器函式簽名,是理解一切的基礎。
// go-iterator-basics/main.go
// 執行環境: Go 1.24+ / 無額外依賴
package main
import (
"fmt"
"iter"
)
// Range 生成整數序列迭代器
func Range(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := range n {
if !yield(i) {
return
}
}
}
}
// RangeWithStep 生成帶步長的整數序列
func RangeWithStep(start, end, step int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := start; i < end; i += step {
if !yield(i) {
return
}
}
}
}
// Enumerate 為任意切片新增索引
func Enumerate[T any](items []T) iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
for i, item := range items {
if !yield(i, item) {
return
}
}
}
}
// Take 只取前n個元素
func Take[T any](n int, seq iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
count := 0
for v := range seq {
if count >= n {
return
}
if !yield(v) {
return
}
count++
}
}
}
// ToPull 將Push迭代器轉換為Pull迭代器
func ToPull[T any](seq iter.Seq[T]) func() (T, bool) {
next, stop := iter.Pull(seq)
return func() (T, bool) {
v, ok := next()
return v, ok
}
}
func main() {
fmt.Println("=== 基本迭代器 ===")
for i := range Range(5) {
fmt.Print(i, " ") // 0 1 2 3 4
}
fmt.Println()
fmt.Println("=== 帶步長 ===")
for i := range RangeWithStep(0, 20, 5) {
fmt.Print(i, " ") // 0 5 10 15
}
fmt.Println()
fmt.Println("=== 帶索引 ===")
fruits := []string{"蘋果", "香蕉", "橘子"}
for i, fruit := range Enumerate(fruits) {
fmt.Printf("%d: %s ", i, fruit)
}
fmt.Println()
fmt.Println("=== 提前退出 ===")
for v := range Take(3, Range(100)) {
fmt.Print(v, " ") // 0 1 2
}
fmt.Println()
fmt.Println("=== Pull迭代器 ===")
pull := ToPull(Range(3))
for {
v, ok := pull()
if !ok {
break
}
fmt.Print(v, " ") // 0 1 2
}
fmt.Println()
}
模式二:泛型迭代器
泛型讓迭代器成為真正的「一等公民」——一個Filter函式可以過濾任何型別的序列。
// go-generic-iterator/main.go
// 執行環境: Go 1.24+ / 無額外依賴
package main
import (
"fmt"
"iter"
"strings"
)
// Filter 過濾序列中的元素
func Filter[T any](seq iter.Seq[T], predicate func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
for v := range seq {
if predicate(v) {
if !yield(v) {
return
}
}
}
}
}
// Map 對序列中的每個元素進行變換
func Map[T any, R any](seq iter.Seq[T], transform func(T) R) iter.Seq[R] {
return func(yield func(R) bool) {
for v := range seq {
if !yield(transform(v)) {
return
}
}
}
}
// FlatMap 對每個元素變換後展平
func FlatMap[T any, R any](seq iter.Seq[T], transform func(T) iter.Seq[R]) iter.Seq[R] {
return func(yield func(R) bool) {
for v := range seq {
for r := range transform(v) {
if !yield(r) {
return
}
}
}
}
}
// Reduce 將序列歸約為單個值
func Reduce[T any, R any](seq iter.Seq[T], initial R, accumulator func(R, T) R) R {
result := initial
for v := range seq {
result = accumulator(result, v)
}
return result
}
// Chunk 將序列分塊
func Chunk[T any](size int, seq iter.Seq[T]) iter.Seq[[]T] {
return func(yield func([]T) bool) {
chunk := make([]T, 0, size)
for v := range seq {
chunk = append(chunk, v)
if len(chunk) == size {
if !yield(chunk) {
return
}
chunk = make([]T, 0, size)
}
}
if len(chunk) > 0 {
yield(chunk)
}
}
}
// Distinct 去重
func Distinct[T comparable](seq iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
seen := make(map[T]bool)
for v := range seq {
if !seen[v] {
seen[v] = true
if !yield(v) {
return
}
}
}
}
}
func main() {
numbers := slicesToIter([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
squares := Map(evens, func(n int) int { return n * n })
first3 := Take(3, squares)
fmt.Print("鏈式操作: ")
for v := range first3 {
fmt.Print(v, " ") // 4 16 36
}
fmt.Println()
sum := Reduce(slicesToIter([]int{1, 2, 3, 4, 5}), 0,
func(acc, n int) int { return acc + n })
fmt.Println("求和:", sum) // 15
fmt.Print("分塊: ")
for chunk := range Chunk(3, slicesToIter([]int{1, 2, 3, 4, 5, 6, 7})) {
fmt.Print(chunk, " ")
}
fmt.Println()
fmt.Print("去重: ")
for v := range Distinct(slicesToIter([]string{"go", "rust", "go", "python", "rust"})) {
fmt.Print(v, " ")
}
fmt.Println()
}
func slicesToIter[T any](s []T) iter.Seq[T] {
return func(yield func(T) bool) {
for _, v := range s {
if !yield(v) {
return
}
}
}
}
模式三:樹/圖遍歷迭代器
迭代器最強大的應用場景——將遞迴遍歷變成可組合的管道。
// go-tree-iterator/main.go
// 執行環境: Go 1.24+ / 無額外依賴
package main
import (
"fmt"
"iter"
)
type TreeNode[T any] struct {
Value T
Left *TreeNode[T]
Right *TreeNode[T]
}
// InOrder 中序遍歷迭代器(左-根-右)
func (n *TreeNode[T]) InOrder() iter.Seq[T] {
return func(yield func(T) bool) {
n.inOrderHelper(yield)
}
}
func (n *TreeNode[T]) inOrderHelper(yield func(T) bool) bool {
if n == nil {
return true
}
if !n.Left.inOrderHelper(yield) {
return false
}
if !yield(n.Value) {
return false
}
if !n.Right.inOrderHelper(yield) {
return false
}
return true
}
// PreOrder 前序遍歷迭代器(根-左-右)
func (n *TreeNode[T]) PreOrder() iter.Seq[T] {
return func(yield func(T) bool) {
n.preOrderHelper(yield)
}
}
func (n *TreeNode[T]) preOrderHelper(yield func(T) bool) bool {
if n == nil {
return true
}
if !yield(n.Value) {
return false
}
if !n.Left.preOrderHelper(yield) {
return false
}
if !n.Right.preOrderHelper(yield) {
return false
}
return true
}
// LevelOrder 層序遍歷迭代器(BFS)
func (n *TreeNode[T]) LevelOrder() iter.Seq[T] {
return func(yield func(T) bool) {
if n == nil {
return
}
queue := []*TreeNode[T]{n}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
if !yield(current.Value) {
return
}
if current.Left != nil {
queue = append(queue, current.Left)
}
if current.Right != nil {
queue = append(queue, current.Right)
}
}
}
}
// Graph 圖定義
type Graph[T comparable] struct {
adjList map[T][]T
}
func NewGraph[T comparable]() *Graph[T] {
return &Graph[T]{adjList: make(map[T][]T)}
}
func (g *Graph[T]) AddEdge(from, to T) {
g.adjList[from] = append(g.adjList[from], to)
}
// DFS 深度優先遍歷迭代器
func (g *Graph[T]) DFS(start T) iter.Seq[T] {
return func(yield func(T) bool) {
visited := make(map[T]bool)
g.dfsHelper(start, visited, yield)
}
}
func (g *Graph[T]) dfsHelper(node T, visited map[T]bool, yield func(T) bool) bool {
if visited[node] {
return true
}
visited[node] = true
if !yield(node) {
return false
}
for _, neighbor := range g.adjList[node] {
if !g.dfsHelper(neighbor, visited, yield) {
return false
}
}
return true
}
// BFS 廣度優先遍歷迭代器
func (g *Graph[T]) BFS(start T) iter.Seq[T] {
return func(yield func(T) bool) {
visited := make(map[T]bool)
queue := []T{start}
visited[start] = true
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
if !yield(node) {
return
}
for _, neighbor := range g.adjList[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
}
}
func main() {
root := &TreeNode[int]{
Value: 4,
Left: &TreeNode[int]{Value: 2, Left: &TreeNode[int]{Value: 1}, Right: &TreeNode[int]{Value: 3}},
Right: &TreeNode[int]{Value: 6, Left: &TreeNode[int]{Value: 5}, Right: &TreeNode[int]{Value: 7}},
}
fmt.Print("中序: ")
for v := range root.InOrder() { fmt.Print(v, " ") }
fmt.Println()
fmt.Print("前序: ")
for v := range root.PreOrder() { fmt.Print(v, " ") }
fmt.Println()
fmt.Print("層序: ")
for v := range root.LevelOrder() { fmt.Print(v, " ") }
fmt.Println()
g := NewGraph[string]()
g.AddEdge("A", "B")
g.AddEdge("A", "C")
g.AddEdge("B", "D")
g.AddEdge("C", "D")
g.AddEdge("D", "E")
fmt.Print("DFS: ")
for v := range g.DFS("A") { fmt.Print(v, " ") }
fmt.Println()
fmt.Print("BFS: ")
for v := range g.BFS("A") { fmt.Print(v, " ") }
fmt.Println()
}
模式四:迭代器組合與管道
迭代器最優雅的用法——像Unix管道一樣組合資料處理流程。
// go-iterator-pipeline/main.go
// 執行環境: Go 1.24+ / 無額外依賴
package main
import (
"fmt"
"iter"
"strings"
)
// Concat 連線多個迭代器
func Concat[T any](seqs ...iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
for _, seq := range seqs {
for v := range seq {
if !yield(v) { return }
}
}
}
}
// Interleave 交替輸出多個迭代器的元素
func Interleave[T any](seqs ...iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
pulls := make([]func() (T, bool), len(seqs))
stops := make([]func(), len(seqs))
for i, seq := range seqs {
pulls[i], stops[i] = iter.Pull(seq)
}
defer func() { for _, stop := range stops { stop() } }()
active := len(seqs)
for active > 0 {
for i, pull := range pulls {
v, ok := pull()
if !ok { pulls[i] = nil; active--; continue }
if !yield(v) { return }
}
}
}
}
// TakeWhile 在條件為true時取值
func TakeWhile[T any](seq iter.Seq[T], predicate func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
for v := range seq {
if !predicate(v) { return }
if !yield(v) { return }
}
}
}
// DropWhile 跳過條件為true的元素
func DropWhile[T any](seq iter.Seq[T], predicate func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
dropping := true
for v := range seq {
if dropping && predicate(v) { continue }
dropping = false
if !yield(v) { return }
}
}
}
// 日誌處理管道
type LogEntry struct {
Timestamp string
Level string
Message string
}
func ParseLogs(rawLines iter.Seq[string]) iter.Seq[LogEntry] {
return Map(rawLines, func(line string) LogEntry {
parts := strings.SplitN(line, " ", 3)
if len(parts) < 3 { return LogEntry{Message: line} }
return LogEntry{Timestamp: parts[0], Level: parts[1], Message: parts[2]}
})
}
func FilterByLevel(level string, logs iter.Seq[LogEntry]) iter.Seq[LogEntry] {
return Filter(logs, func(l LogEntry) bool { return l.Level == level })
}
func ExtractMessages(logs iter.Seq[LogEntry]) iter.Seq[string] {
return Map(logs, func(l LogEntry) string { return l.Message })
}
func main() {
fmt.Print("連線: ")
for v := range Concat(slicesToIter([]int{1, 2, 3}), slicesToIter([]int{4, 5, 6})) {
fmt.Print(v, " ")
}
fmt.Println()
fmt.Print("交替: ")
for v := range Interleave(slicesToIter([]string{"A", "B", "C"}), slicesToIter([]string{"1", "2", "3"})) {
fmt.Print(v, " ")
}
fmt.Println()
rawLogs := slicesToIter([]string{
"2026-01-01 ERROR 資料庫連線失敗",
"2026-01-01 INFO 服務啟動",
"2026-01-01 ERROR 快取超時",
"2026-01-01 WARN 記憶體使用率高",
})
fmt.Println("\n=== 日誌管道:只看ERROR訊息 ===")
for msg := range ExtractMessages(FilterByLevel("ERROR", ParseLogs(rawLogs))) {
fmt.Println(" ", msg)
}
}
func slicesToIter[T any](s []T) iter.Seq[T] {
return func(yield func(T) bool) {
for _, v := range s { if !yield(v) { return } }
}
}
func Filter[T any](seq iter.Seq[T], predicate func(T) bool) iter.Seq[T] {
return func(yield func(T) bool) {
for v := range seq {
if predicate(v) { if !yield(v) { return } }
}
}
}
func Map[T any, R any](seq iter.Seq[T], transform func(T) R) iter.Seq[R] {
return func(yield func(R) bool) {
for v := range seq { if !yield(transform(v)) { return } }
}
}
func Take[T any](n int, seq iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
count := 0
for v := range seq {
if count >= n { return }
if !yield(v) { return }
count++
}
}
}
模式五:生產級迭代器模式
將迭代器應用於真實的生產場景:資料庫分頁、檔案串流處理、併發迭代。
// go-production-iterator/main.go
// 執行環境: Go 1.24+ / 無額外依賴
package main
import (
"bufio"
"fmt"
"iter"
"strings"
)
// 分頁迭代器
type Page[T any] struct {
Items []T
PageNum int
HasMore bool
}
func Paginate[T any](fetchPage func(pageNum int) (Page[T], error)) iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
pageNum := 0
for {
page, err := fetchPage(pageNum)
if err != nil { return }
for _, item := range page.Items {
if !yield(pageNum, item) { return }
}
if !page.HasMore { return }
pageNum++
}
}
}
// 檔案行迭代器
func LinesFromString(s string) iter.Seq[string] {
return func(yield func(string) bool) {
scanner := bufio.NewScanner(strings.NewReader(s))
for scanner.Scan() {
if !yield(scanner.Text()) { return }
}
}
}
// 滑動視窗迭代器
func SlidingWindow[T any](size int, seq iter.Seq[T]) iter.Seq[[]T] {
return func(yield func([]T) bool) {
window := make([]T, 0, size)
for v := range seq {
window = append(window, v)
if len(window) > size { window = window[1:] }
if len(window) == size {
snapshot := make([]T, size)
copy(snapshot, window)
if !yield(snapshot) { return }
}
}
}
}
// 有序去重
func Deduplicate[T comparable](seq iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
var prev T
first := true
for v := range seq {
if first || v != prev {
first = false
prev = v
if !yield(v) { return }
}
}
}
}
func main() {
// 分頁迭代器
fmt.Println("=== 分頁迭代器 ===")
mockFetchPage := func(pageNum int) (Page[string], error) {
allItems := [][]string{{"使用者1", "使用者2", "使用者3"}, {"使用者4", "使用者5"}, {"使用者6"}}
if pageNum >= len(allItems) { return Page[string]{HasMore: false}, nil }
return Page[string]{Items: allItems[pageNum], PageNum: pageNum, HasMore: pageNum < len(allItems)-1}, nil
}
for pageNum, user := range Paginate(mockFetchPage) {
fmt.Printf(" 頁%d: %s\n", pageNum, user)
}
// 滑動視窗
fmt.Println("\n=== 滑動視窗 ===")
for window := range SlidingWindow(3, slicesToIter([]int{1, 2, 3, 4, 5, 6, 7})) {
fmt.Print(window, " ")
}
fmt.Println()
// 有序去重
fmt.Print("有序去重: ")
for v := range Deduplicate(slicesToIter([]int{1, 1, 2, 2, 3, 3, 3, 4})) {
fmt.Print(v, " ")
}
fmt.Println()
}
func slicesToIter[T any](s []T) iter.Seq[T] {
return func(yield func(T) bool) {
for _, v := range s { if !yield(v) { return } }
}
}
避坑指南:5個常見陷阱
坑1:迭代器中修改底層集合
// ❌ 錯誤:遍歷時修改切片導致未定義行為
for i, v := range mySlice {
mySlice = append(mySlice, v) // 危險!
}
// ✅ 正確:先複製再遍歷
copied := make([]int, len(mySlice))
copy(copied, mySlice)
for v := range slicesToIter(copied) { /* 安全 */ }
坑2:Pull迭代器忘記呼叫stop
// ❌ 錯誤:Pull後未呼叫stop
next, _ := iter.Pull(mySeq) // stop被忽略了!
// ✅ 正確:始終defer stop
next, stop := iter.Pull(mySeq)
defer stop()
坑3:迭代器重用導致意外行為
// ❌ 錯誤:迭代器是消耗性的
seq := Range(5)
for v := range seq { fmt.Print(v) } // 0 1 2 3 4
for v := range seq { fmt.Print(v) } // 無輸出!
// ✅ 正確:每次建立新迭代器
for v := range Range(5) { fmt.Print(v) }
for v := range Range(5) { fmt.Print(v) }
坑4:遞迴迭代器棧溢位
深度遞迴樹可能導致棧溢位,改用顯式棧(層序遍歷方式)實現。
坑5:goroutine洩漏
Buffered迭代器中消費者提前退出時,確保done channel能通知生產者退出。
報錯排查表
| 報錯資訊 | 原因 | 解決方案 |
|---|---|---|
cannot range over xxx |
型別不是iter.Seq/iter.Seq2 | 確認函式簽名 |
yield is not used |
迭代器函式中未呼叫yield | 確保呼叫yield(v) |
cannot use seq as iter.Seq |
迭代器簽名不匹配 | 檢查iter.Seq[T]vsiter.Seq2[K,V] |
panic: range over nil |
迭代器函式為nil | 新增nil檢查 |
goroutine leak |
Pull迭代器未呼叫stop | defer stop() |
stack overflow |
遞迴迭代器深度過大 | 改用顯式棧迭代 |
deadlock |
迭代器內部channel阻塞 | 新增context取消或超時機制 |
unexpected address |
yield回傳的是副本 | Go值語義,需要取地址請用指標*T |
iterator consumed twice |
迭代器只能消耗一次 | 每次建立新迭代器 |
invalid memory address |
迭代器中存取已關閉資源 | 確保資源在迭代完成後才關閉 |
進階最佳化:5個生產級技巧
技巧1:迭代器與context整合
func ContextAware[T any](ctx context.Context, seq iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
next, stop := iter.Pull(seq)
defer stop()
for {
select {
case <-ctx.Done():
return
default:
v, ok := next()
if !ok { return }
if !yield(v) { return }
}
}
}
}
技巧2:並行迭代器
使用goroutine和channel實現ParallelMap,並行處理迭代器元素。
技巧3:迭代器效能基準
使用Go benchmark對比迭代器與傳統for迴圈的效能差異。
技巧4:自訂集合實作iter介面
為OrderedMap等自訂集合實作All()、Keys()、Values()迭代器方法。
技巧5:迭代器中介軟體模式
func WithMetrics[T any](name string, seq iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
count := 0
start := time.Now()
for v := range seq {
count++
if !yield(v) {
log.Printf("[ITER:%s] interrupted after %d items", name, count)
return
}
}
log.Printf("[ITER:%s] completed %d items, elapsed: %v", name, count, time.Since(start))
}
}
Go迭代器 vs 其他語言對比
| 維度 | Go 1.24 iter | Python Generator | Rust Iterator | Java Stream |
|---|---|---|---|---|
| 型別 | func(yield func(V) bool) |
yield v |
trait Iterator |
Stream<T> |
| 泛型 | ✅ 完整泛型 | ❌ 動態型別 | ✅ 完整泛型 | ✅ 完整泛型 |
| 懶求值 | ✅ 天然懶 | ✅ 天然懶 | ✅ 天然懶 | ✅ 可选拉/推 |
| 提前退出 | ✅ yield回傳false | ✅ break | ✅ take/scan | ⚠️ 需短路操作 |
| 組合性 | ✅ 函式組合 | ✅ 生成器組合 | ✅ adapter組合 | ✅ 串流操作 |
| 併發安全 | ❌ 非併發安全 | ❌ GIL限制 | ❌ 非併發安全 | ✅ 平行串流 |
| 效能開銷 | 低(函式呼叫) | 高(解釋執行) | 零成本抽象 | 中(裝箱/拆箱) |
| 學習曲線 | 中 | 低 | 中高 | 中 |
| 生態成熟度 | 新(1.23引入) | 成熟 | 成熟 | 成熟 |
總結
Go 1.24的range over func迭代器是Go語言表達力的重要提升:
- 基礎模式:理解
iter.Seq[T]和iter.Seq2[K,V]兩種簽名,掌握yield回傳false的提前退出機制 - 泛型迭代器:Filter/Map/Reduce/Chunk等工具函式讓資料處理像管道一樣優雅
- 樹/圖遍歷:將遞迴遍歷封裝為迭代器,可組合、可中斷、可複用
- 迭代器管道:Concat/Interleave/TakeWhile/DropWhile實現資料流組合
- 生產級模式:分頁迭代器、檔案串流、滑動視窗、帶context取消
關鍵原則:迭代器是消耗性的、非併發安全的、天然懶求值的。理解這三點,就能避免90%的坑。
線上工具推薦
- /zh-TW/json/format - JSON格式化,處理迭代器回傳的JSON資料
- /zh-TW/dev/curl-to-code - cURL轉程式碼,快速生成Go HTTP迭代器
- /zh-TW/encode/hash - 雜湊計算,迭代器去重與校驗
- /zh-TW/text/diff - 文字對比,對比迭代器版本變更
本站提供瀏覽器本地工具,免註冊即可試用 →
#Go 1.24#迭代器#range over func#泛型迭代器#Go新特性#2026#编程语言