The [Go 1.23 release candidates](https://golang.org/dl/go1.23rc2) are out. That means it's time for another entry in the What's New in Go series where I talk about my contribution to this release. For Go 1.23, I proposed (but did not implement) four new methods: [reflect.Value.Seq](https://pkg.go.dev/reflect@go1.23rc2#Value.Seq), [reflect.Value.Seq2](https://pkg.go.dev/reflect@go1.23rc2#Value.Seq2), [reflect.Type.CanSeq](https://pkg.go.dev/reflect@go1.23rc2#Type.CanSeq), and [reflect.Type.CanSeq2](https://pkg.go.dev/reflect@go1.23rc2#Type.CanSeq2). So, where do they come from and what do they do? - - - - If you've been following what's new in Go 1.23 at all, you've probably heard about new generic custom iteration functions. The signature for writing these iterators is a little tricky, but how you use them is simple. [Here's an example](https://go.dev/play/p/xwy_XX9KEu8?v=gotip): ```go // counter counts up from zero func counter() iter.Seq[int] { // let's omit this for now } func main() { for n := range counter() { if n > 5 { break } fmt.Println(n) } } ``` The output is what you would expect: ``` 0 1 2 3 4 5 ``` If using a custom iterator is simple, how do you implement it? First, let's define a similar function in Python: ```python def counter(): n = 0 while True: yield n n += 1 ``` And in JavaScript: ```javascript function* counter() { let n = 0; while (true) { yield n; n++; } } ``` [Now the Go version](https://go.dev/play/p/xwy_XX9KEu8?v=gotip): ```go // counter counts up from zero func counter() iter.Seq[int] { return func(yield func(int) bool) { n := 0 for { if !yield(n) { return } n++ } } } ``` What's interesting about the Go version of iterators is that there is no special syntax for creating an iterator function like `yield` or `function *`. The type [iter.Seq](https://pkg.go.dev/iter#Seq) is a generic named type for `func(yield func(V) bool)` where `V` is `any` type. In other words, an iterator is just a regular function that receives a callback. The callback is called with values from the stream of iterated values and either returns `true` or `false`. If it returns `true`, the iterator should return the next value in the sequence. If it returns `false`, the iterator should do any necessary cleanup (none in this case) and stop iterating. (Incidentally, in Go 1.24, this is likely to be able to be written as `return func(yield iter.Yield[int]) {` once [generic type aliases](https://github.com/golang/go/issues/46477#issuecomment-2253068457) are available. The old signature will still work, but the new one is slightly easier to write and read.) - - - - You could already write something a lot like iterators with Go channels. In fact, I wrote a blog post about this [ten (!) years ago](/post/on-using-go-channels-like-python-generators/). The channel-based iterators in that blog post had three major problems: 1. Because Go did not have generics at that time, you had to write new utilities for each type you wanted to stream. 2. Because they used channels and goroutines, each value sent and received had to be scheduled by the Go runtime scheduler, which is slow. 3. Because a channel should be either written to or read from in one place but not both, you needed to pass around cancel channels everywhere to signal that a generator should exit or you'll be stuck with a lot of zombie goroutines. Problem 1 has since been solved. Problem 2 is harder to solve, but in theory, it might be possible to optimize away some calls to the runtime scheduler under some circumstances. (Indeed, this is how some iterators now [work under the hood](https://research.swtch.com/coro).) But problem 3 was really fatal for the idea of using channels as iterators. Here is how the use of a quit channel looks using some code adapted from my blog post of ten years ago: ```go func sendOrFail(n int64, out chan<- int64, quit <-chan struct{}) bool { select { case out <- n: return true case <-quit: return false } } func counter(quit <-chan struct{}) <-chan int64 { out := make(chan int64) go func() { defer close(out) var n int64 if ok := sendOrFail(n, out, quit); !ok { return } n++ }() return out } ``` The blog post predated the invention of `context.Context`, so you could clean it up a little today by using a context instead of a quit channel, but the end result would be the same. You need to constantly pass around a close token to know when to stop iterating, and this gets quite complicated as you nest iterators. Here's what using that could look like: ```go ctx, cancel := context.WithCancel(context.Background()) for n := range counter(ctx.Done()) { if n > 5 { break } fmt.Println(n) } cancel() // tell counter to close or it will leak forever! ``` Indeed if you took `sendOrFail` and tried to make it a little more generic so it takes a context and can automatically create and close the channel, [it might look like this](https://go.dev/play/p/F4jpCwPlDCF?v=gotip): ```go func buildChannel[T any](ctx context.Context, callback func(func(T) bool)) <-chan T { ch := make(chan T) yield := func(v T) bool { select { case ch <- v: return true case <-ctx.Done(): return false } } go func() { defer close(ch) callback(yield) }() return ch } func counter(ctx context.Context) <-chan int64 { return buildChannel(ctx, func(yield func(int64) bool) { var n int64 for { if !yield(n) { return } n++ } }) } ``` In other words, you end up with the same signature as Go 1.23 iterators, just with an extra wrapper function and context argument! Using the custom iterators actually added in Go 1.23 and writing `return func(yield func(int) bool)` is a little bit awkward, but it's much better than needing to pass around close tokens and use them at every call site. - - - - We've seen that Go iterators show that we live in [the best of all possible worlds](https://en.wikipedia.org/wiki/Candide) (or [maybe not](https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md)). What about [reflect.Value.Seq](https://pkg.go.dev/reflect@go1.23rc2#Value.Seq), [reflect.Value.Seq2](https://pkg.go.dev/reflect@go1.23rc2#Value.Seq2), [reflect.Type.CanSeq](https://pkg.go.dev/reflect@go1.23rc2#Type.CanSeq), and [reflect.Type.CanSeq2](https://pkg.go.dev/reflect@go1.23rc2#Type.CanSeq2)? The doc string for `reflect.Value.Seq` says, > `Seq` returns an `iter.Seq[Value]` that loops over the elements of v. If v's kind is `Func`, it must be a function that has no results and that takes a single argument of type `func(T) bool` for some type T. If v's kind is `Pointer`, the pointer element type must have kind `Array`. Otherwise v's kind must be `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64`, `Uintptr`, `Array`, `Chan`, `Map`, `Slice`, or `String`. Calling the `Seq` method is like calling `for x := range v { xValue := reflect.ValueOf(x) }` for an arbitrary type v. The `CanSeq` method just reports whether a type can be used as an iterator. Possibly surprisingly, since it works just like a range statement, the value yielded by the `Seq` method when iterating over a slice [will just be an integer representing the index of each slice item](https://go.dev/play/p/fKiOM_eoo7b?v=gotip). ```go func printEach(x any) { v := reflect.ValueOf(x) if !v.Type().CanSeq() { panic("bad type") } for x := range v.Seq() { fmt.Println(x.Interface()) } } func main() { printEach([]string{"a", "b", "c"}) } ``` Output: ``` 0 1 2 ``` To get the slice elements, [use `Seq2` and the second range value](https://go.dev/play/p/nj09BZ2a0rz?v=gotip): ```go func printEach(x any) { v := reflect.ValueOf(x) if !v.Type().CanSeq2() { panic("bad type") } for x, y := range v.Seq2() { fmt.Println(x.Interface(), y.Interface()) } } func main() { printEach([]string{"a", "b", "c"}) } ``` Output: ``` 0 a 1 b 2 c ``` The reflect package already had a method for iterating over a map, [reflect.Value.MapRange](https://pkg.go.dev/reflect#Value.MapRange), but using it is not quite as nice as using `reflect.Value.Seq2`: ```go iter := reflect.ValueOf(m).MapRange() for iter.Next() { k := iter.Key() v := iter.Value() // ... } // vs. for k, v := range reflect.ValueOf(m).Seq2() { // ... } ``` - - - - How did I come to propose these new methods? First, Github user [gophun](https://github.com/gophun) asked about [how to call an iterator of an unknown type](https://github.com/golang/go/issues/61897#issuecomment-1959960853), for example when marshaling JSON. The answer was to write a little bit of reflect code. I [opened an issue](https://github.com/golang/go/issues/66056) to add it to the reflect package itself, and once the proposal was approved (with improved method names suggested by [Ian Lance Taylor](https://github.com/ianlancetaylor)), user [qiulaidongfeng](https://github.com/qiulaidongfeng) created a changelist to implement it before I got around to doing it. (This makes it the opposite of [reflect.TypeFor](/post/2024/golang-reflect-type-for/), where I just implemented [Josh Bleecher Snyder's proposal](https://github.com/golang/go/issues/60088) after coming up with the name for the function.) There was a subsequent issue to [add support for range funcs to the Go template package](https://github.com/golang/go/issues/66107) that didn't make it into Go 1.23, but should be in Go 1.24 that relies on this new feature of the reflect package. - - - - That's what I worked on for Go 1.23. The creation of Go itself is an iterative process, so I hope to return in six months to write about Go 1.24 as the next development cycle loops again.