Fundamentals 29 min read

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.

Go Programming World
Go Programming World
Go Programming World
Unlocking Go 1.23 Iterators: Deep Dive into Push & Pull Mechanisms

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)) bool

The 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 => 3

This 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=false

Pull 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

邮箱: [email protected]

博客: https://jianghushinian.cn

GitHub: https://github.com/jianghushinian

Pull iterator diagram
Pull iterator diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

IteratorsprogrammingGocoroutinePull IteratorPush Iterator
Go Programming World
Written by

Go Programming World

Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.