3 min read
Handling errors during iteration with range-over-func
Yesterday we looked at a range-over-func iterator that was missing a vital piece: Error handling during iteration. I know of three possible solutions to this problem, and today we’ll look at the simplest of them, which I typically recommend: Using iter.Seq2. We’ve already looked at this pattern from the consumer’s perspective. Today we’ll see how the implementation works. func grep(r io.Reader, pattern string) (iter.Seq2[string, error], error) { re, err := regexp.
2 min read
Implementing a range-over-func iterator
It’s finally time to look at how we implement a range-over-func iterator. I’d venture a guess this should be the go-to pattern for most iterators, unless or until you have special needs that it won’t address. And, of course, we’ll discuss some of those in the near future, as well. First, here’s how our grep implementation looks using range-over-func: func grep(r io.Reader, pattern string) (iter.Seq[string], error) { re, err := regexp.
2 min read
Separating iteration from advancement
Last week we looked at a simple custom iterator pattern: type Result struct {/* ... */} func (*Result) Next() (string, bool) func (*Result) Err() error But let’s talk about a few variations, and when they might make sense. First, I already mentioned last week that in some cases we could simply eliminate the bool value if the zero value of the iterated value can serve as an indication that iteration has completed.
3 min read
Building a custom iterator
We’ve looked at using channels as iterators, and found they’re hardly ideal. Let’s look at the next obvious answer: custom iterators. Before range-over-func, which we’ll get to next, custom iterators were really the only meaningful solution. And they still remain a very viable one, because of their great flexibility. Let’s start with a simple implementation of a custom iterator version of our grep function (see it in the playground): type Result struct { re *regexp.
3 min read
Closing a channel iterator early
I’ve been away for a while. Last week I spoke at GoWest 2025, and have been just generally busy. But now I’m ready to pick up on the topic I started nearly two weeks ago: Drawbacks of channel-based iterators! In addition to the issue of error handling with a channel-based iterator, there’s the potentially stickier issue of how to abort iterating early. To illustrate, let’s look at some code that consumes our grep iterator, but stops processing after the first result:
3 min read
Implementing a channel-based iterator
Today let’s look at re-implementing our non-iterating grep function using a channel for iteration. First the code: func grep(r io.Reader, pattern string) (chan <- string, error) { re, err := regexp.Compile(pattern) if err != nil { return nil, err } matches := make(chan string) go func() { defer close(matches) scanner := bufio.NewScanner(r) var matches []string for scanner.Scan() { line := scanner.Text() if re.MatchString(line) { matches <- line } } if err := scanner.
1 min read
Implementing iterators
The last few weeks I’ve been talking about different iterator patterns, but from the perspective of consuming iterators. Let’s switch angles now, and begin talking about how to implement iterators. To illustrate, let’s use a simple example of a grep utility. It will read an io.Reader, and return any lines that match a regular expression input. Here’s a simple implementation, that returns all results at once (no iteration): func grep(r io.
Subscribe to Boldly Go: Daily
Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.
Unsure? Browse the archive.
4 min read
Errors with range over func
Last week I introduced the topic of range-over-func as an iterator pattern. Let’s look at how error handling differs with this approach. For context, let’s compare the custom iterator approach, which has three distinct errors to check: 1. The initial function call, 2. while iterating over each row, 3. finally at the end, to ensure iteration completed successfully. orders, err := db.Orders(ctx, userID) if err != nil { return err } defer orders.
2 min read
Range over func
Since Go 1.23, we’ve had a new way we can implement iterators. I’ve written previously about it if you’re interested. But today we’ll take a glance at how it affects our Orders example: orders, err := db.Orders(ctx, userID) if err != nil { return err } for order, err := range orders { if err != nil { return err } /* Do something with each order */ } Unless you’re already quite familiar with the range-over-func feature, it’s probably not immediately clear what’s going on here, just by looking at the code.
2 min read
Custom iterators
Until recently, custom iterators were probably the most common way to iterate over a list of elements that might trigger an error. Several examples exist in the standard library. Perhaps the most well known would be the sql.Rows type, which provides (among others), the following methods: Next() bool — Advances to the next item Scan(...any) error — Processes the current item Err() error — Reports an iteration error Close() error — Closes iterator, possibly before the last item has been processed These four methods are pretty standard, in any custom iterator implementation, though Scan() will typically be replaced with an implementation-specific method to process the current result, and in some cases Err() and Close() may be combined.
3 min read
Iterating over channels
For this discussion of iterators, let’s establish a baseline example. It’s made up, but realistic and common: A database method that returns all user orders. We’ll be experimenting with different function signatures, but in general, this is what we can imagine it will look like: func (DB) Orders(ctx context.Context, userID string) ([]*Order, error) And we would consume it with code something like this: orders, err := db.Orders(ctx, userID) if err !