What is iter.Pull, and when do you need it? Implement a generic Zip.
Question 141HardGo 1.22 to 1.25
Range-over-func iterators are push iterators: the sequence drives the loop. That makes it impossible to step through two sequences in lockstep with a single for. iter.Pull converts a push iterator into a pull pair, next() (V, bool) and stop(). Internally it uses a coroutine that switches cheaply between goroutines, so it costs more than a plain range loop but far less than a channel.
type Pair[A, B any] struct {
First A
Second B
}
func Zip[A, B any](as iter.Seq[A], bs iter.Seq[B]) iter.Seq[Pair[A, B]] {
return func(yield func(Pair[A, B]) bool) {
nextA, stopA := iter.Pull(as)
defer stopA()
nextB, stopB := iter.Pull(bs)
defer stopB()
for {
a, ok1 := nextA()
b, ok2 := nextB()
if !ok1 || !ok2 {
return
}
if !yield(Pair[A, B]{a, b}) {
return
}
}
}
}
names := slices.Values([]string{"a", "b", "c"})
ages := slices.Values([]int{1, 2})
for p := range Zip(names, ages) {
fmt.Println(p.First, p.Second) // a 1, b 2
}
Gotchas:
- Always call
stop, usually withdefer. Otherwise the paused iterator never finishes, its deferred cleanup never runs, and the resources leak. - Do not call
nextconcurrently from several goroutines. - Prefer plain range when a single sequence is enough.
More on Generics
- Q139Which generic helpers in
slicesandmapsshould a senior Go developer know, and what are their gotchas? - Q140How do generics combine with Go 1.23 iterators (
iter.Seq)? Write lazyFilterandMapadapters and explain theyieldcontract. - Q142What are generic type aliases (Go 1.24), and how do they differ from generic type definitions?
- Q143Are generic types covariant? Can a
Box[Dog]be used as aBox[Animal], or aBox[int]converted to aBox[MyInt]? - Q144What is an "instantiation cycle"? Why does this recursive generic function fail to compile?
- Q145What does this print? (
%Tand reflection on generic types)