Go 1.23 introduced a new feature, range over function types or more commonly known as "iterators".
What's special about these is the way control flow works. The loop body is converted to a function, passed to the interator, but a return (or any other change in control flow) from the loop body skips through enclosing iterator, while still calling defers, affecting control flow of the outer function. Example:
1package main
2
3import "fmt"
4
5func Iterator(yield func() bool) {
6 fmt.Println("start")
7 defer fmt.Println("done")
8
9 yield()
10}
11
12func foo() {
13 for range Iterator {
14 fmt.Println("inside loop")
15 return
16 }
17 fmt.Println("outside loop")
18}
19
20func ExampleIterator() {
21 // Output:
22 // start
23 // inside loop
24 // done
25 foo()
26}
What's this useful for?
Well it's a lot like python's with
blocks:
1package main
2
3import (
4 "io"
5 "os"
6)
7
8func OpenFile(name string) func(func(*os.File, error) bool) {
9 return func(yield func(*os.File, error) bool) {
10 f, err := os.Open(name)
11 if err == nil {
12 defer f.Close()
13 }
14 yield(f, err)
15 }
16}
17
18func main() {
19 for f, err := range OpenFile("hello.txt") {
20 if err != nil {
21 // handle error
22 }
23 _, _ = io.ReadAll(f)
24 }
25}
Or something like:
1package main
2
3import "sync"
4
5type mutex struct {
6 m sync.Mutex
7}
8
9func (m *mutex) do(yield func() bool) {
10 m.m.Lock()
11 defer m.m.Unlock()
12 yield()
13}
14
15func main() {
16 var mu mutex
17 for range mu.do {
18 // do protected thing
19 }
20
21 // old way
22 mu.do(func() bool {
23 // do protected thing
24 })
25}