Recently, while programming an RPC broker layer in go, I needed an iterator for a custom class I had created. In python, I would have just overridden __next__() (python3), and in java, I would have implemeted Iterable, but go, which tries to be very minimalist has no easy way to create an iterator.

Natively, the range built-in allows for easy iteration of slices, arrays, maps, and channels like so:

for value := range mychannel {
  // do something
}

So to give my class an iterator, I used channels! For this example, consider Array to be type I need to iterate over and Value to be the types that come out of the iterator. Heres our type definition and use case

type Iterator struct {
  C chan Value
  closer chan bool
}

for v := range array.Iter().C {
  // do something
}

And here’s the Iter() function definition.

type Array []Value // In practice this is more complicated

func (self Array) Iter() *Iterator {
  c := make(chan Value)
  closer := make(chan bool)
  iter := &Iterator{c, closer}

  // Spew out the iterator in a goroutine
  go func() {
    for _, v := range self {
      select {
        case c <- v:
        case <- closer:
          close(c)
          return
      }
    }
    close(c)
    close(closer)
  }()

  return iter
}

Cool! Now whenever, I call array.Iter(), I’ll get a channel that will give me all the values in order. In practice, you wouldn’t do this with a type that maps to a slice, but something more complicated which couldn’t be used with the range built-in by itself.

Theres only one problem with this implementation. What happens if we break off the list? We can’t have goroutines and channels sitting unused. So we’ve already implemented the necessary stuff for the channel closer in Iter(), now we need only a Break() function (and a use case):

func (i *Iterator) Break() {
  select {
    case _, ok := <- i.closer:
      // closer has been closed
    default:
      // closer is still open
      i.closer <- true
  }
}

iter := array.Iter()
for v := range iter.C {
  // do something
  if true {
    iter.Break()
    break
  }
}

This implementation shows how channels can be used to create iterators over any type in Go with minimal overhead and while keeping abstractions in place. For a working demo, checkout this example on play.golang.org