How to use iter in Go 1.23
What is Iterator in Go?
In development terminology, an “iterator” refers to an object or interface that provides sequential access to elements within a collection (e.g., arrays, lists, trees, etc.). An iterator helps in processing or traversing through the elements of a collection by visiting each element one at a time.
In Go, instead of using a direct iterator pattern, the range
keyword is primarily used to iterate over collections such as slices or maps.
How to use Iterator in Go?
iter is a packge officially introduced in Go 1.23 for iterating over sequences. However, by setting GOEXPERIMENT=rangefunc
and using go build
or go test
, it is possible to temporarily use this feature in version 1.22 as well.
Therefore, you can either use the Go 1.22.x version with the experimental feature, the 1.23 release candidate, or the official 1.23 version that will be released in August.
How is the iter
package implemented?
iter package is so simple, it includes the following functions and types.
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
type coro struct{}
//go:linkname newcoro runtime.newcoro
func newcoro(func(*coro)) *coro
//go:linkname coroswitch runtime.coroswitch
func coroswitch(*coro)
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())
Today, we’ll take a brief look at Seq
and Seq2
. You can find a simple example of how to use this here.
How to use iter.Seq, iter.Seq2?
By implement functions that return these types, you can iterate over your own custom sequences.
Let’s look at an example of a simple ordered map implementation (note: in practice, an ordered map is not implemented this simply).
With no iter.Seq
package ordered_map
type OrderedMap[K comparable, V any] struct {
keys []K
items map[K]V
}
func New[K comparable, V any](capacity int) *OrderedMap[K, V] {
return &OrderedMap[K, V]{
keys: make([]K, 0, capacity),
items: make(map[K]V, capacity),
}
}
// Add new items by key, value
func (om *OrderedMap[K, V]) Add(k K, v V) {
om.keys = append(om.keys, k)
om.items[k] = v
}
// Get present item by key
func (om *OrderedMap[K, V]) Get(k K) V {
return om.items[k]
}
// Retrieve all keys for iteration.
func (om *OrderedMap[K, V]) Keys() []K {
return om.keys
}
With the implementation described above, if you want to insert some elements and then retrieve all elements in order, you would need to write the code as shown below.
type MyStruct struct {
id int
name string
}
func (ms MyStruct) String() string {
return fmt.Sprintf("ID: %d, Name: %s", ms.id, ms.name)
}
func TestOrderedMap(t *testing.T) {
t.Run("print all elements in order.", func(t *testing.T) {
om := ordered_map.New[string, MyStruct](10)
om.Add("key1", MyStruct{id: 1, name: "test1"})
om.Add("key3", MyStruct{id: 3, name: "test3"})
om.Add("key2", MyStruct{id: 2, name: "test2"})
for _, k := range om.Keys() {
fmt.Printf("Value: %s\n", om.Get(k))
}
})
}
First, retrieve all the keys, then use om.Get(k)
to access the OrderedMap
again and list the values.
What if you use iter.Seq
?
When you only need value
// only iterate values when use this function
func (om *OrderedMap[K, V]) Values() iter.Seq[V] {
return func(yield func(V) bool) {
for _, k := range om.keys {
if !yield(om.Get(k)) {
return
}
}
}
}
// ...
t.Run("print all elements in order using iter.Seq.", func(t *testing.T) {
om := ordered_map.New[string, MyStruct](10)
om.Add("key1", MyStruct{id: 1, name: "test1"})
om.Add("key3", MyStruct{id: 3, name: "test3"})
om.Add("key2", MyStruct{id: 2, name: "test2"})
for v := range om.Values() {
fmt.Printf("Values: %s\n", v)
}
})
When you need key with value
// iterate key and value
func (om *OrderedMap[K, V]) KeyValues() iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for _, k := range om.keys {
if !yield(k, om.Get(k)) {
return
}
}
}
}
// ...
t.Run("print all keys and elements in order using iter.Seq.", func(t *testing.T) {
om := ordered_map.New[string, MyStruct](10)
om.Add("key1", MyStruct{id: 1, name: "test1"})
om.Add("key3", MyStruct{id: 3, name: "test3"})
om.Add("key2", MyStruct{id: 2, name: "test2"})
for k, v := range om.KeyValues() {
fmt.Printf("Key: %s | Values: %s\n", k, v)
}
})
Another Case
If you want to iterate over only specific fields in a struct, you can do it like this.
type MyStructs []MyStruct
func (ms MyStructs) Names() iter.Seq[string] {
return func(yield func(string) bool) {
for _, s := range ms {
if !yield(s.name) {
return
}
}
}
}
type MyStruct struct {
id int
name string
}
t.Run("print only name in struct", func(t *testing.T) {
var myStructs MyStructs = MyStructs{
MyStruct{id: 1, name: "test1"},
MyStruct{id: 3, name: "test3"},
MyStruct{id: 2, name: "test2"},
}
for name := range myStructs.Names() {
fmt.Printf("Name: %s\n", name)
}
})
Conclsion
While the example above might not be ideal, with Go 1.23, the iter
package allows for more diverse operations, including iterating over specific fields of a struct. As a beginner, I can't fully predict how extensively it will be used, but I hope it inspires someone to implement various data structures. 😊