Overview

In today’s post, I’d like to summarize the improvements and changes made in Golang 1.22 for iterators.

Referencing loop variables in goroutines

Below is code to print the strings “a”, “b”, and “c” in sequence through a loop. The only difference is that we are using a goroutine inside the loop, and we don’t want the function to exit first, so we use a channel to wait.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	done := make(chan bool)
 7
 8	values := []string{"a", "b", "c"}
 9	for _, v := range values {
10		go func() {
11			fmt.Println(v)
12			done <- true
13		}()
14	}
15
16	// wait for all goroutines to complete before exiting
17	for range values {
18		<-done
19	}
20}

Result: c is printed 3 times

1$ go run main.go
2c
3c
4c

However, the result is not what you would expect: the last value is printed three times, because the goroutine refers to the last variable in the loop. In my previous development, I actually encountered this case, so I solved it with a workaround like below.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	done := make(chan bool)
 7
 8	values := []string{"a", "b", "c"}
 9	for _, v := range values {
10		t := v
11
12		go func() {
13			fmt.Println(t)
14			done <- true
15		}()
16	}
17
18	// wait for all goroutines to complete before exiting
19	for range values {
20		<-done
21	}
22}

This was done by reassigning the variable inside the clause and passing it around, rather than referencing the loop variable when outputting from inside the goroutine. At the time, I didn’t think this was a problem, just something to be aware of in asynchronous programming.

The Golang developers have been aware of this issue for a while now, and in 1.22 they made a change to make references in loops unambiguous: after 1.22, if a goroutine runs inside a loop, the variable it references doesn’t change!

Done in Golang 1.22 Using

 1$gvm use go1.22.0
 2$gvm list
 3
 4gvm gos (installed)
 5
 6   go1.21.0
 7=> go1.22.0
 8   system
 9
10$go run main.go
11c
12b
13a

Integer loops

Previously, if you wanted to iterate over a certain number of times, Go would require you to do something like this

1package main
2
3import "fmt"
4
5func main() {
6	for i := 0; i < 5; i++ {
7		fmt.Println(i)
8	}
9}

However, starting in 1.22, it is possible to implement iterations with a single integer value as simple as

1package main
2
3import "fmt"
4
5func main() {
6	for i := range 5 {
7		fmt.Println(i)
8	}
9}

GOEXPERIMENT

GOEXPERIMENT` is an environment variable that allows Go language developers to experimentally test new features or optimizations. It is designed to enable or disable various experimental features in the Go compiler and runtime. It is primarily used internally during Go’s development process, and is useful for evaluating the performance impact of certain features or testing the stability of new features.

rangefunc

In 1.22, a function called rangefunc is available internally, which implements a function iteration in the form of recursive calls to functions. Below is an example code.

 1package main
 2
 3import (
 4	"fmt"
 5	"strings"
 6)
 7
 8func Split(s string) func(func(int, string) bool) {
 9	parts := strings.Split(s, " ")
10	return func(f func(int, string) bool) {
11		for i, p := range parts {
12			if !f(i, p) {
13				break
14			}
15		}
16	}
17}
18
19func main() {
20	str := "hello my name is penguin"
21	for i, x := range Split(str) {
22		fmt.Println(i, x)
23	}
24}
1GOEXPERIMENT=rangefunc go run main.go
20 hello
31 my
42 name
53 is
64 penguin

It’s an interesting idea, but personally, I don’t think it looks that attractive… I don’t think it’s good because I don’t think it’s Go. (This is a personal opinion)

Summary

We’ve seen the changes in Golang 1.22 related to iterations through the example code, and explained the GOEXPERIENCE variable and one experimental function (rangefunc) added in 1.22 as an example.

Translated with DeepL.com (free version)