How to use iter in Go 1.23

the korean guy
4 min readAug 13, 2024

--

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. 😊

--

--

No responses yet