How do range-over-func iterators (Go 1.23) work? What does this print?
Question 36HardGo 1.22 to 1.25
func Countdown(n int) iter.Seq[int] { // iter.Seq[V] is func(yield func(V) bool)
return func(yield func(int) bool) {
defer fmt.Println("iterator cleanup")
for i := n; i >= 0; i-- {
if !yield(i) {
return // the consumer stopped early
}
}
}
}
func main() {
for v := range Countdown(5) {
if v == 2 {
break
}
fmt.Print(v, " ")
}
fmt.Println("after loop")
}
Output:
5 4 3 iterator cleanup
after loop
Since Go 1.23, for range accepts functions of the form func(yield func() bool), func(yield func(V) bool) and func(yield func(K, V) bool). The compiler turns the loop body into the yield callback. yield returns true to ask for more values. A break, return, goto or panic out of the body makes yield return false. The iterator must then stop, and its own defers run before the loop statement finishes. That is why "iterator cleanup" prints before "after loop".
Rules and gotchas:
- An iterator that calls
yieldagain after it returnedfalsetriggers a run-time panic:range function continued iteration after function for loop body returned false. Always check the result ofyield. return, labeledbreak/continueanddeferinside the loop body keep their normal meaning. Adeferin the body belongs to the enclosing function, not to the iterator.- These are "push" iterators.
iter.Pullconverts one into a "pull" iterator (next, stop), which is useful for zipping two sequences. Always callstop. - The standard library provides them:
slices.All,slices.Values,slices.Backward,maps.Keys,maps.All, and in Go 1.24strings.Lines,strings.SplitSeqandbytes.SplitSeq. - The loop variable follows the Go 1.22 per-iteration rules, so capturing it in a closure is safe.
More on Language Fundamentals & Types
- Q34How does Go handle integer overflow, division, and conversions between numeric types?
- Q35How did loop variable semantics change in Go 1.22, and what does this print?
- Q37How do struct field alignment and padding work? What are the sizes of these structs?
- Q38What is the difference between a method value and a method expression? What does this print?
- Q39What type does an untyped constant take in a non-constant shift? What does this print?