Unlocking Go 1.23 Iterators: Deep Dive into Push & Pull Mechanisms
This comprehensive guide explores Go 1.23's iterator system, detailing the underlying principles of push and pull iterators, code transformations performed by the compiler, coroutine implementation, and practical examples that compare Go's approach with Python and JavaScript, helping developers master iterator usage and design.
Continuing from the previous article "万字长文:彻底掌握 Go 1.23 中的迭代器——使用篇", we now dive deeper into the internal principles of Go iterators to achieve a complete grasp of Go iterator features.
迭代器原理
Now is the time to learn the principles of Go iterators, exploring their essence to fully master Go iterator characteristics.
Iterators are higher‑order functions that accept a function ( yield) as a parameter, whose signature can be one of three function types:
func(func()) bool
func(func(V)) bool
func(func(K, V)) boolThe iterator function controls the for‑range iteration process. The for‑range loop can start an iterator, and the iterator calls the yield function to pass each generated value to the caller ( for‑range loop). The logic inside the for‑range block defines how the yield function should handle each value; if the block contains break, continue, or return, the yield function returns false, otherwise it returns true.
The above description roughly outlines the iterator workflow, but it can be a bit convoluted, so read it several times to deepen understanding.
Consider the following code as an example to illustrate the iterator's underlying mechanism:
package main
import "fmt"
func iterator(slice []int) func(yield func(i int, v int) bool) {
return func(yield func(i int, v int) bool) {
for i, v := range slice {
if !yield(i, v) {
return
}
}
}
}
func main() {
s := []int{1, 2, 3, 4, 5}
iterator(s)(func(i, v int) bool {
fmt.Printf("%d => %d
", i, v)
return true
})
}Running the example yields:
$ go run main.go
0 => 1
1 => 2
2 => 3This result matches expectations.
The most confusing part of Go iterators is that iterator(s) returns a plain function func(yield func(i int, v int) bool), yet it can be iterated.
Even if we pass an empty function that matches the iterator type to for‑range, the Go program will not report an error.
Below is a simplified version of the compiler‑rewritten code, sufficient for understanding the iterator's core principle:
for i, v := range iterator(s) {
if i == 3 {
break
}
fmt.Printf("%d => %d
", i, v)
}The compiler rewrites it to:
iterator(s)(func(i, v int) bool {
if i == 3 {
return false
}
fmt.Printf("%d => %d
", i, v)
return true
})Thus, the iterator is essentially syntactic sugar; the actual execution remains a function call.
Push & Pull 迭代器
Although we have learned iterator principles, Go iterators come in two types.
So far, all iterators discussed are Push iterators. A Push iterator controls iteration progress internally and pushes elements to the yield function.
Go also provides Pull iterators, which use a next() function for the caller to actively pull elements and can be explicitly terminated with stop().
Example of a Pull iterator using the iter package:
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) { ... }
func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func()) { ... }These functions convert Push iterators to Pull iterators. The returned next retrieves the next value, while stop explicitly ends the iteration.
next, stop := iter.Pull2(iterator(s))
for {
i, v, ok := next()
if !ok { break }
fmt.Printf("i=%d v=%d ok=%t
", i, v, ok)
}
stop()Output:
$ go run main.go
i=0 v=1 ok=true
i=1 v=2 ok=true
i=0 v=0 ok=falsePull iterators are powerful but cannot be used directly with for‑range; they must be converted back to Push iterators.
Pull 迭代器原理
The iter package defines a coro struct and two linked functions newcoro and coroswitch that connect to the runtime package, implementing lightweight coroutines.
type coro struct{}
//go:linkname newcoro runtime.newcoro
func newcoro(func(*coro)) *coro
//go:linkname coroswitch runtime.coroswitch
func coroswitch(*coro)These coroutines are lighter than goroutines and behave like Python coroutines.
The simplified Pull implementation (core collaboration logic) is:
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) {
var (
v V
ok bool
done bool
yieldNext bool
)
c := newcoro(func(c *coro) {
yield := func(v1 V) bool {
if done { return false }
if !yieldNext { panic("iter.Pull: yield called again before next") }
yieldNext = false
v, ok = v1, true
coroswitch(c)
return !done
}
seq(yield)
var v0 V
v, ok = v0, false
done = true
})
next = func() (v1 V, ok1 bool) {
if done { return }
yieldNext = true
coroswitch(c)
return v, ok
}
stop = func() {
if !done {
done = true
coroswitch(c)
}
}
return next, stop
}This code mirrors Python's await/yield behavior, allowing the iterator to pause and resume execution.
更优雅的迭代器实现
Comparing Go iterators with other languages:
Go 迭代器
package main
import "fmt"
func iterator(n int) iter.Seq[int] {
return func(yield func(v int) bool) {
for i := 0; i < n; i++ {
if !yield(i) { return }
}
}
}
func main() {
for v := range iterator(5) { fmt.Println(v) }
}Python 迭代器
def generator(num: int):
for i in range(num):
yield i
for value in generator(5):
print(value)JavaScript 迭代器
function* generator(num) {
for (let i = 0; i < num; i++) {
yield i;
}
}
for (const v of generator(5)) {
console.log(v);
}All three languages use the yield keyword, but Go's implementation is more complex due to compiler transformations.
迭代器到底在解决什么问题
Go iterators solve two main problems:
Unified iteration interface, addressing ecosystem fragmentation.
Hiding implementation details, decoupling traversal logic from data structures.
However, they also introduce complexity for developers who must implement their own iterators.
示例:使用迭代器读取文件
package main
import (
"bufio"
"fmt"
"iter"
"os"
"strings"
)
// Implementation 1: load entire file (may overflow memory)
func ProcessFile1(filename string) {
data, _ := os.ReadFile(filename)
lines := strings.Split(string(data), "
")
for i, line := range lines {
fmt.Printf("line %d: %s
", i, line)
}
}
// Implementation 2: use bufio scanner
func ProcessFile2(filename string) {
file, _ := os.Open(filename)
defer file.Close()
scanner := bufio.NewScanner(file)
i := 0
for scanner.Scan() {
fmt.Printf("line %d: %s
", i, scanner.Text())
i++
}
}
// Implementation 3: Go 1.23 iterator
func ReadLines(filename string) iter.Seq2[int, string] {
return func(yield func(int, string) bool) {
file, _ := os.Open(filename)
defer file.Close()
scanner := bufio.NewScanner(file)
i := 0
for scanner.Scan() {
if !yield(i, scanner.Text()) { return }
i++
}
}
}
func ProcessFile3(filename string) {
for i, line := range ReadLines(filename) {
fmt.Printf("line %d: %s
", i, line)
}
}
func main() {
filename := "demo/main.go"
ProcessFile1(filename)
fmt.Println("--------------")
ProcessFile2(filename)
fmt.Println("--------------")
ProcessFile3(filename)
}Running the program produces identical output for all three implementations, demonstrating the practicality of Go iterators.
For further reading, see the extensive list of references below.
延伸阅读
wiki/迭代器: https://zh.wikipedia.org/wiki/迭代器
Go Wiki: Rangefunc Experiment:https://go.dev/wiki/RangefuncExperiment
Go Wiki: Range Clauses:https://go.dev/wiki/Range
The Go Programming Language Specification: For statements with range clause:https://go.dev/ref/spec#For_range
Range over int:https://groups.google.com/g/golang-nuts/c/7J8FY07dkW0
Range Over Function Types:https://go.dev/blog/range-functions
Go 1.23 Release Notes:https://go.dev/doc/go1.23
iter Documentation:https://pkg.go.dev/[email protected]
slices Documentation:https://pkg.go.dev/[email protected]
Why People are Angry over Go 1.23 Iterators:https://www.gingerbill.org/article/2024/06/17/go-iterator-design/
proposal: Go 2: iterators #40605:https://github.com/golang/go/issues/40605
proposal: Go 2: function values as iterators #43557:https://github.com/golang/go/issues/43557
discussion: standard iterator interface #54245:https://github.com/golang/go/discussions/54245
user-defined iteration using range over func values #56413:https://github.com/golang/go/discussions/56413
spec: add range over int, range over func #61405:https://github.com/golang/go/issues/61405
iter: new package for iterators #61897:https://github.com/golang/go/issues/61897
x/exp/xiter: new package with iterator adapters #61898:https://github.com/golang/go/issues/61898
src/cmd/compile/internal/rangefunc/rewrite.go:https://github.com/golang/go/blob/go1.23.0/src/cmd/compile/internal/rangefunc/rewrite.go
Coroutines for Go:https://research.swtch.com/coro
Coroutines for Go (HN discussion):https://news.ycombinator.com/item?id=36762682
Go 1.23中的自定义迭代器与iter包:https://tonybai.com/2024/06/24/range-over-func-and-package-iter-in-go-1-23/
Python 迭代器:https://docs.python.org/zh-cn/3.13/tutorial/classes.html#iterators
JavaScript 迭代器和生成器:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators
A look at iterators in Go:https://medium.com/eureka-engineering/a-look-at-iterators-in-go-f8e86062937c
本文 GitHub 示例代码:https://github.com/jianghushinian/blog-go-example/tree/main/iterator
本文永久地址:https://jianghushinian.cn/2025/07/17/go-iterator/
联系我
公众号: Go编程世界
微信: jianghushinian
博客: https://jianghushinian.cn
GitHub: https://github.com/jianghushinian
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
