淺談 Go Iterator

Go 1.23 引入了原生的 Iterator 支援,這是 Go 語言在函數式程式設計道路上的重要里程碑。這篇將分享 Go Iterator 的設計理念、使用方法和實踐。
什麼是 Iterator?
Iterator(迭代器)是一個**函數**,它將序列中的連續元素傳遞給 callback 函數(通常命名為 yield)。當序列結束或 yield 返回 false 時,函數會停止迭代。
在 Go 1.23 中,Iterator 是語言的原生特性,介面定義在 iter 套件中。
Iterator 的核心設計
類型定義
Go 定義了兩種主要的 Iterator 類型:
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
yield 函數的語意設計
yield 函數的核心語意是:"產出並詢問是否繼續"
// yield 函數返回值的意義:
// - true: 繼續迭代下一個元素
// - false: 停止迭代
func myIterator() iter.Seq[int] {
return func(yield func(int) bool) {
for i := 1; i <= 5; i++ {
if !yield(i) { // 產出值並詢問是否繼續
return // 尊重消費者的決定
}
}
}
}
協作式控制流
Iterator 的設計體現了生產者和消費者之間的協作關係:
生產者視角的語意
func fibonacci() iter.Seq[int] {
return func(yield func(int) bool) {
a, b := 0, 1
for {
// 語意:"我計算出了一個值,你要嗎?"
if !yield(a) {
// 語意:"好的,你不要了,我停止生產"
return
}
// 語意:"你還要,我繼續計算下一個"
a, b = b, a+b
}
}
}
消費者視角的語意
// range 迴圈的隱含語意
for value := range fibonacci() {
fmt.Println(value)
if value > 100 {
// 隱含語意:"我不需要更多了"
break // 這會讓 yield 返回 false
}
// 隱含語意:"我處理完了,請給我下一個"
}
基本使用方法
1. 簡單的數值範圍迭代器
package main
import (
"fmt"
"iter"
)
// 整數範圍迭代器
func rangeSeq(start, end int) iter.Seq[int] {
return func(yield func(int) bool) {
for i := start; i <= end; i++ {
if !yield(i) {
return
}
}
}
}
func main() {
// 使用 range 語法遍歷
for num := range rangeSeq(1, 5) {
fmt.Println(num) // 輸出: 1, 2, 3, 4, 5
}
}
實際用途:
測試資料生成:快速生成測試用的數字序列
分頁處理:生成頁碼序列
2. Key-Value Pair 迭代器
// Map 迭代器
func mapSeq[K comparable, V any](m map[K]V) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for k, v := range m {
if !yield(k, v) {
return
}
}
}
}
// 使用
data := map[string]int{"apple": 5, "banana": 3, "cherry": 8}
for key, value := range mapSeq(data) {
fmt.Printf("%s: %d\n", key, value)
}
實際用途:
配置處理:遍歷配置項目
資料轉換:將 map 轉換為其他格式
條件過濾:在遍歷過程中進行篩選
3. 集合類型的 Iterator
type Set[V comparable] struct {
items map[V]struct{}
}
// 按照命名慣例,集合的迭代器方法命名為 All
func (s *Set[V]) All() iter.Seq[V] {
return func(yield func(V) bool) {
for item := range s.items {
if !yield(item) {
return
}
}
}
}
// 使用
set := &Set[string]{items: map[string]struct{}{
"apple": {}, "banana": {}, "cherry": {},
}}
for item := range set.All() {
fmt.Println(item)
}
進階應用
1. Pull 函數 - 手動控制迭代
func demonstratePull() {
seq := rangeSeq(1, 5)
// 轉換為 Pull 迭代器
next, stop := iter.Pull(seq)
defer stop() // 重要:必須調用 stop 來清理資源
// 手動拉取值
for {
value, ok := next()
if !ok {
break // 序列結束
}
fmt.Printf("Pulled: %d\n", value)
// 可以在這裡添加複雜的邏輯
if value == 3 {
fmt.Println("Stopping early")
break
}
}
}
實際用途:
API 分頁處理:需要根據回應決定是否繼續
資料庫批次讀取:根據記憶體使用量控制讀取
檔案處理:逐行處理大檔案,可隨時停止
2. 配對迭代器
// 將序列中的值配對
func Pairs[V any](seq iter.Seq[V]) iter.Seq2[V, V] {
return func(yield func(V, V) bool) {
next, stop := iter.Pull(seq)
defer stop()
for {
v1, ok1 := next()
if !ok1 {
return
}
v2, ok2 := next()
// 如果 ok2 為 false,v2 是零值;產生最後一對
if !yield(v1, v2) {
return
}
if !ok2 {
return
}
}
}
}
// 使用範例:座標處理
coordinates := []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}
coordSeq := SliceSeq(coordinates)
for x, y := range Pairs(coordSeq) {
point := Point{X: x, Y: y}
drawPoint(point)
}
// 結果:(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)
3. Filter 和 Map 操作
// Filter 迭代器
func Filter[V any](seq iter.Seq[V], predicate func(V) bool) iter.Seq[V] {
return func(yield func(V) bool) {
for value := range seq {
if predicate(value) {
if !yield(value) {
return
}
}
}
}
}
// Map 迭代器
func Map[T, U any](seq iter.Seq[T], mapper func(T) U) iter.Seq[U] {
return func(yield func(U) bool) {
for value := range seq {
if !yield(mapper(value)) {
return
}
}
}
}
// 鏈式操作
numbers := rangeSeq(1, 10)
evenNumbers := Filter(numbers, func(n int) bool { return n%2 == 0 })
squares := Map(evenNumbers, func(n int) int { return n * n })
for square := range squares {
fmt.Printf("%d ", square) // 4 16 36 64 100
}
4. 無限序列
// 費波那契數列
func Fibonacci() iter.Seq[int] {
return func(yield func(int) bool) {
a, b := 0, 1
for {
if !yield(a) {
return
}
a, b = b, a+b
}
}
}
// 使用時需要手動控制停止
func useFibonacci() {
for num := range Fibonacci() {
if num > 1000 {
break
}
fmt.Println(num)
}
}
// 產生唯一 ID
func UniqueIDs() iter.Seq[string] {
return func(yield func(string) bool) {
counter := 0
for {
id := fmt.Sprintf("ID_%d_%d", time.Now().Unix(), counter)
if !yield(id) {
return
}
counter++
}
}
}
實際用途:
測試資料:生成無限測試資料
演算法實現:實現需要無限序列的演算法
5. 批次處理
// 將序列分批處理
func Batch[T any](seq iter.Seq[T], size int) iter.Seq[[]T] {
return func(yield func([]T) bool) {
batch := make([]T, 0, size)
for item := range seq {
batch = append(batch, item)
if len(batch) == size {
if !yield(batch) {
return
}
batch = batch[:0] // 重用 slice
}
}
// 處理剩餘元素
if len(batch) > 0 {
yield(batch)
}
}
}
常用輔助函數
// 將 slice 轉換為 Iterator
func SliceSeq[T any](slice []T) iter.Seq[T] {
return func(yield func(T) bool) {
for _, item := range slice {
if !yield(item) {
return
}
}
}
}
// 將 Iterator 收集為 slice
func Collect[T any](seq iter.Seq[T]) []T {
var result []T
for item := range seq {
result = append(result, item)
}
return result
}
// 取前 n 個元素
func Take[T any](seq iter.Seq[T], n int) iter.Seq[T] {
return func(yield func(T) bool) {
count := 0
for item := range seq {
if count >= n {
return
}
if !yield(item) {
return
}
count++
}
}
}
// 跳過前 n 個元素
func Skip[T any](seq iter.Seq[T], n int) iter.Seq[T] {
return func(yield func(T) bool) {
count := 0
for item := range seq {
if count < n {
count++
continue
}
if !yield(item) {
return
}
}
}
}
命名慣例
根據官方文件,Iterator 的命名有以下慣例:
1. 集合迭代器
// 集合類型的主要迭代器命名為 All
func (s *Set[V]) All() iter.Seq[V] { ... }
// 多種序列時,名稱指示具體序列
func (c *Country) Cities() iter.Seq[*City] { ... }
func (c *Country) Languages() iter.Seq[string] { ... }
2. 配置參數的迭代器
// 需要額外配置的迭代器
func (m *Map[K, V]) Scan(min, max K) iter.Seq2[K, V] { ... }
func Split(s, sep string) iter.Seq[string] { ... }
3. 不同順序的迭代器
// 指示遍歷順序
func (l *List[V]) All() iter.Seq[V] { ... } // 從頭到尾
func (l *List[V]) Backward() iter.Seq[V] { ... } // 從尾到頭
func Preorder(root Node) iter.Seq[Node] { ... } // 前序遍歷
性能考量
延遲求值的優勢
// 延遲求值範例:只計算需要的部分
func expensiveComputation() iter.Seq[int] {
return func(yield func(int) bool) {
for i := 1; i <= 1000000; i++ {
// 昂貴的計算
result := complexCalculation(i)
if !yield(result) {
return // 可以提早停止,節省計算
}
}
}
}
// 只取前 5 個結果,剩下的不會計算
results := Take(expensiveComputation(), 5)
for result := range results {
fmt.Println(result)
}
記憶體使用效率
// 傳統方式:需要將所有結果載入記憶體
func processAllAtOnce() []ProcessedData {
data := loadLargeDataset() // 可能很大
var results []ProcessedData
for _, item := range data {
results = append(results, process(item))
}
return results // 記憶體使用量可以說"翻倍"
}
// Iterator 方式:串流處理
// 用到哪,拉取到哪
func processStream() iter.Seq[ProcessedData] {
return func(yield func(ProcessedData) bool) {
for item := range loadDataStream() {
processed := process(item)
if !yield(processed) {
return
}
}
}
}
推薦的錯誤處理方式
// 方法 1:使用 Result 類型
type Result[T any] struct {
Value T
Error error
}
func ProcessWithErrors[T, U any](seq iter.Seq[T], processor func(T) (U, error)) iter.Seq[Result[U]] {
return func(yield func(Result[U]) bool) {
for item := range seq {
value, err := processor(item)
if !yield(Result[U]{Value: value, Error: err}) {
return
}
}
}
}
// 方法 2:分離錯誤處理
func ProcessSafe[T, U any](seq iter.Seq[T], processor func(T) (U, error)) (iter.Seq[U], error) {
var firstError error
filtered := func(yield func(U) bool) {
for item := range seq {
value, err := processor(item)
if err != nil {
if firstError == nil {
firstError = err
}
continue
}
if !yield(value) {
return
}
}
}
return filtered, firstError
}
與傳統 Iterator Pattern 的比較
相同點
目的相同:提供統一的方式遍歷集合
封裝性:隱藏集合內部實現
順序訪問:按順序訪問元素
主要差異
| 特性 | 傳統 Iterator Pattern | Go Iterator |
| 實現方式 | 物件導向(介面 + 類別) | 函數式(closure + yield) |
| 狀態管理 | Iterator 物件維護狀態 | closure 維護狀態 |
| 語法支援 | 手動調用 Next(), HasNext() | 原生 range 語法 |
| 記憶體開銷 | 需要創建 Iterator 物件 | 只有函數調用開銷 |
| 組合性 | 較難組合多個迭代器 | 容易組合和鏈接 |
| 類型安全 | 依賴語言的泛型支援 | 完整的泛型支援 |
傳統 Iterator Pattern
// 傳統方式
type Iterator[T any] interface {
HasNext() bool
Next() T
}
type SliceIterator[T any] struct {
slice []T
index int
}
func (it *SliceIterator[T]) HasNext() bool {
return it.index < len(it.slice)
}
func (it *SliceIterator[T]) Next() T {
if it.HasNext() {
value := it.slice[it.index]
it.index++
return value
}
var zero T
return zero
}
// 使用
func useTraditionalIterator() {
data := []int{1, 2, 3, 4, 5}
iterator := &SliceIterator[int]{slice: data}
for iterator.HasNext() {
fmt.Println(iterator.Next())
}
}
Go Iterator
func SliceSeq[T any](slice []T) iter.Seq[T] {
return func(yield func(T) bool) {
for _, item := range slice {
if !yield(item) {
return
}
}
}
}
// 使用
func useGoIterator() {
data := []int{1, 2, 3, 4, 5}
for item := range SliceSeq(data) {
fmt.Println(item)
}
}
Go 標準庫中已經實現的 Iterator
除了下述的 database Next()、filepath.Walk(Dir)、sync Map Range()。其實更早之前都已經提供了 Iterator 的自我實現,也不難發現現有的 iterator 設計跟使用方式都不相同,所以 Go 才想統一標準。
Go database 的 Next()
Go 資料庫的 Next() 實現了與 Iterator 相同的迭代模式和控制流語義。
rows.Next() 的實際行為
// rows.Next() 的實際行為
func (rs *Rows) Next() bool {
// 1. 釋放上一行的記憶體鎖定
rs.closemuRUnlockIfHeldByScan()
// 2. 檢查 context 是否已取消
if rs.contextDone.Load() != nil {
return false
}
// 3. 嘗試讀取下一行(這裡才真正從網路/磁碟讀取)
var doClose, ok bool
withLock(rs.closemu.RLocker(), func() {
doClose, ok = rs.nextLocked() // 關鍵:這裡才讀取下一筆
})
// 4. 如果需要關閉就關閉
if doClose {
rs.Close()
}
return ok
}
實際的資料流與控制流
func demonstrateActualFlow() {
// 1. Query 只是建立連線和發送 SQL,但不等待所有結果
rows, err := db.Query("SELECT * FROM huge_table") // 只發送查詢,不讀取資料
if err != nil {
log.Fatal(err)
}
defer rows.Close()
fmt.Println("查詢已發送,但還沒讀取任何資料")
// 2. 第一次 Next() 開始從緩衝區讀取,緩衝區空時才觸發網路讀取
if rows.Next() { // 可能觸發網路讀取,填充緩衝區
fmt.Println("讀取到第一筆資料")
var id int
var name string
rows.Scan(&id, &name) // 掃描剛才讀取的資料
fmt.Printf("第一筆: %d, %s\n", id, name)
}
// 3. 每次 Next() 都會讀取下一筆
if rows.Next() { // 通常從緩衝區直接讀取
fmt.Println("讀取到第二筆資料")
// ... 掃描第二筆
}
// 4. 如果我們在這裡 early break,剩下的資料不會被讀取
fmt.Println("停止讀取,剩下的資料不會從資料庫傳輸")
}
lib/pq 的實做方式
func networkBehavior() {
// PostgreSQL (lib/pq)
rows, _ := pgDB.Query("SELECT * FROM million_rows")
// lib/pq 實際行為:
// 1. 發送查詢到 PostgreSQL
// 2. PostgreSQL 開始執行查詢並逐行發送結果
// 3. TCP 將多個小的 DataRow 消息組合成 package(通常4-8KB)
// 4. lib/pq 的 bufio.Reader 緩衝這些package
// 5. rows.Next() 從 bufio 緩衝區逐行解析,緩衝區空時才觸發網路讀取
count := 0
for rows.Next() { // 每次 Next() 會:
// - 從 bufio 緩衝區讀取並解析一行(通常很快)
// - 當緩衝區數據用完時,觸發網路讀取下一個package(偶爾較慢)
// - 注意:不是應用層的批次緩存,而是網路層的被動緩衝
var data string
rows.Scan(&data)
count++
if count >= 10 {
break // PostgreSQL 可能仍在發送資料,但客戶端停止讀取
// 連接上可能還有未讀的網路資料
}
}
}
總結
🎯 核心影響
Go 1.23 引入統一的 Iterator 標準是一個重大的生態系統改進,將混亂的迭代模式統一為 iter.Seq[T] 和 range 語法,帶來函數式程式設計能力和更好的資源效率。
✅ 主要好處
統一體驗:所有迭代都用相同的
range語法函數式能力:支援 Map、Filter、組合等操作
性能優化:延遲求值、恆定記憶體使用、提早退出
開發效率:降低學習成本、提升代碼可讀性
⚠️ 現實挑戰
過渡期混亂:新舊兩套 API 並存,開發者需要同時掌握
決策負擔:每次都要選擇用新方法還是舊方法
生態系統滯後:標準庫和第三方庫需要時間適應
學習資源落差:教學材料、Stack Overflow 答案新舊混雜
這是 Go 語言成熟度的重要里程碑,短期內會帶來一些混亂和學習成本,但長期來看將大幅提升 Go 的表達能力和開發體驗。關鍵是給生態系統足夠的時間來適應和標準化。
建議:新專案採用 Iterator,舊專案漸進式遷移,團隊內部制定統一的使用規範。






