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:
Zero arguments – similar to:
```go
func iter0(yield func() bool)
```go
for range iter0 {
fmt.Println("hello")
}
// Output: hello hello helloOne argument – shown in the previous example (iter1).
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.