Innovating with Go: Range Iterators Demystified

BY Mark Howell 14 July 20246 MINS READ
article cover

Today in Edworking News we want to talk about Introduction We're using Go to write Dolt, the world's first version-controlled SQL
database. Like most large Go codebases, we have a lot of collection types that we iterate over. New
in Go 1.23, you can now use the range keyword to iterate over custom collection types. How does that work? Is it a good idea? Let's dive in.

What are Range Iterators?

Range iterators are function types used with the built-in range keyword, starting in Go 1.23. This new feature significantly expands the power of the range keyword, which could previously only iterate over slices or maps. With range iterators, it is now possible to iterate over custom collection types. From the release notes, the new range capabilities allow for three types of loops: no arguments, one argument, or two arguments.

Example Usage

Here's a simple example to illustrate:
```go
func iter1(yield func(int) bool) bool {
for i := 0; i < 3; i++ {
if !yield(i) {
return false
}
}
return true
}
// Usage
for i := range iter1 {
fmt.Println(i)
}
// Output: 0 1 2
In this example, `iter1` is a range iterator function that runs three times.

Types of Range Iterators

Types of range iterators come in three forms based on the number of arguments they accept:

  1. Zero arguments – similar to:
    ```go
    func iter0(yield func() bool)
    ```go
    for range iter0 {
    fmt.Println("hello")
    }
    // Output: hello hello hello

  2. One argument – shown in the previous example (iter1).

  3. Two arguments – useful for scenarios needing both the index and the element:
    ```go
    func iter2(yield func(int, int) bool) bool

What Does `yield` Do?

The `yield` function within a range iterator invokes the body of the loop. When you write a range loop using an iterator, the Go compiler implicitly converts your code into function calls. For example:
```go
for i := range iter1 {
fmt.Println(i)
}
// Is implicitly converted to:
for {
var i int
var ok bool
i, ok = iter1(yield)
if !ok {
break
}
fmt.Println(i)
}
The yield function also indicates whether the loop should continue. If the result of `yield` isn't checked, the program will panic if there is a break or return statement inside the loop.

Use Cases

Custom Collection Iteration

Range iterators shine when you need to iterate over custom collection types that aren’t maps or slices. For example, if you want to iterate over a collection of elements to filter only prime numbers:
```go
func (c *Collection) IterPrimes(yield func(int) bool) bool {
for _, v := range c.items {
if isPrime(v) {
if !yield(v) {
return false
}
}
}
return true
}
You call it like so:
```go
for prime := range myCollection.IterPrimes {
fmt.Println(prime)
}

Error Handling During Iteration

Another powerful use case is managing errors during I/O operations in iteration. For instance:
```go
func (c *Collection) IterWithErrors(yield func(int, error) bool) bool {
for _, v := range c.items {
elem, err := generateElement(v)
if !yield(elem, err) {
return false
}
}
return true
}

Sentinel Error Handling

Handling sentinel errors, such as `io.EOF`, can be streamlined using range iterators. By wrapping traditional iterators to handle end-of-loop control flows more cleanly:
```go
func (c *Collection) IterHandleSentinels(yield func(int) bool) bool {
for {
item, err := c.iterator.Next()
if err == io.EOF {
return true
}
if err != nil {
return false
}
if !yield(item) {
return false
}
}
}

Extended Use Cases: Pull and Pull2

The Go authors also provided Pull and Pull2 functions to convert range iterator functions back into traditional iterators with `Next()` methods, which can be helpful if you're working with libraries that use these new features but prefer the old iteration method.

Readability and Conventional Considerations

One question that arises with this new flexibility is whether it's confusing for the user if the range loop's return values differ from the standard Go conventions. The community will likely develop best practices over time, but it is worth noting that for many cases, especially with slices, only the value is often of interest, not the index.

Conclusion

To delve deeper and see more examples, visit our GitHub page.

Remember these 3 key ideas for your startup:

  • Optimized Iterations: Using range iterators can enhance your codebase by providing seamless iterations over custom collections.

  • Simplified Error Management: Error handling during iterations becomes cleaner and more intuitive.

  • Enhanced Flexibility: Tailor your iteration needs more precisely to your application's requirements.
    Edworking is the best and smartest decision for SMEs and startups to be more productive. Edworking is a FREE superapp of productivity that includes all you need for work powered by AI in the same superapp, connecting Task Management, Docs, Chat, Videocall, and File Management. Save money today by not paying for Slack, Trello, Dropbox, Zoom, and Notion.
    For more details, see the original source.

article cover
About the Author: Mark Howell Linkedin

Mark Howell is a talented content writer for Edworking's blog, consistently producing high-quality articles on a daily basis. As a Sales Representative, he brings a unique perspective to his writing, providing valuable insights and actionable advice for readers in the education industry. With a keen eye for detail and a passion for sharing knowledge, Mark is an indispensable member of the Edworking team. His expertise in task management ensures that he is always on top of his assignments and meets strict deadlines. Furthermore, Mark's skills in project management enable him to collaborate effectively with colleagues, contributing to the team's overall success and growth. As a reliable and diligent professional, Mark Howell continues to elevate Edworking's blog and brand with his well-researched and engaging content.

Trendy NewsSee All Articles
Try EdworkingA new way to work from  anywhere, for everyone for Free!
Sign up Now