Range Notions You Should Know in Golang

Understanding Go’s Range Loop
explores the tension between pushing culinary boundaries and creating a pleasant dining experience.

All perceiving is also thinking, all reasoning is also intuition, all observation is also invention.
— Rudolf Arnheim

Medium Link: Range Notions You Should Know in Golang | by Wesley Wei | Sep, 2024 | Programmer’s Career
Author:Wesley Wei – Medium

Hello, here is Wesley, Today’s article is about range in Go. Without further ado, let’s get started.💪

1.1 for range Overview and Usage

Introduce the basic syntax and usage scenarios of the for range loop in Go.

The for range loop is a very commonly used looping structure in Go, used to iterate over various collection types such as arrays, slices, maps, strings, and channels. The for range loop can efficiently iterate over these data structures and obtain both the index and value simultaneously.

Basic Syntax

1
for index, value := range collection {}
  • collection: represents the data structure you want to traverse, such as an array, slice, map, string, or channel(only value).
  • index: represents the current element’s index. For maps, the index is the key (key).
  • value: represents the current index position’s value.

If you don’t need the index, you can use the underscore (_) to ignore it:

1
for _, value := range collection {}

1.2 for range Usage Scenarios

  • Traversing arrays and slices: used to access each element in order.
  • Traversing maps: used to access each key-value pair in the map.
  • Traversing strings: used to access each character (including Unicode characters) in the string.
  • Traversing channels: used to receive data from a channel until it is closed.

Iterating over arrays
An array is a fixed-size sequence, and for range can be used to iterate over each element in the array.

1
2
3
4
arr := [5]int{10, 20, 30, 40, 50}
for i, v := range arr {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}

Note: The length of an array is fixed and will not change during the iteration process.

Iterating over slices
A slice can be thought of as a reference to an array. for range has similar applications on slices as it does on arrays, but the length of a slice can dynamically change.

1
2
3
4
slice := []int{100, 200, 300, 400, 500}
for i, v := range slice {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}

Note: A slice can dynamically expand or shrink, so if the contents of the slice change during iteration, it may affect the loop results.
For more notes on slices, please refer to the article:

Slice Notions You Should Know in Golang | by Wesley Wei | Jul, 2024 | Programmer’s Career

Iterating over maps
A map is a collection of key-value pairs, and for range can iterate over each key-value pair in the map.

1
2
3
4
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}

Note: The iteration order of a map is random and does not guarantee the same order each time it runs.

Iterating over strings

In Go, a string is an immutable sequence of bytes. The for range statement can be used to iterate over each character in the string, supporting Unicode characters.

1
2
3
4
str := "Hello, 世界"
for i, r := range str {
fmt.Printf("Index: %d, Rune: %c\n", i, r)
}

Note:

  • When iterating over a string using for range, it processes Unicode characters (rune) rather than bytes. This is crucial for handling multi-byte characters (such as Chinese).
    Iterating over channels

A channel is a concurrency primitive in Go, used to pass data between goroutines. The for range statement can be used to iterate over the values received by a channel before it is closed.

1
2
3
4
5
6
7
8
9
10
ch := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}

Note:

  • The channel must be closed by the producer when it finishes sending data, or else the for range loop will block indefinitely.
  • The for range statement will automatically exit the loop when the channel is closed.

By using for range to iterate over these data structures, Go can easily handle different types of data.

1.3 for range usage errors

Let’s examine two classic examples that are not intuitive and easy to make mistakes for beginners. These examples illustrate common pitfalls when using range loops in Go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// pointer example
type user struct {
name string
age uint64
}
func main() {
u := []user{
{"wesley", 20},
{"wei", 30},
{"wesleywei", 40},
}
n := make([]*user, 0, len(u))
for _, v := range u {
n = append(n, &v)
}
fmt.Println(n)
for _, v := range n {
fmt.Println(v)
}
}

// Closure example
func main() {
var funcs []func()

for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}

for _, f := range funcs {
f()
}
}

In Go versions less than 1.22, the output results like this:

1
2
3
4
5
6
7
8
9
10
// Pointer example output
[0xc00000c048 0xc00000c048 0xc00000c048]
&{wesleywei 40}
&{wesleywei 40}
&{wesleywei 40}

// Closure example output:
3
3
3

Why is this?

For all range loops in Go, the language will assign a new variable ha with the original slice or array at compile-time, and copying occurs during the assignment process. Each element will be assigned to a temporary variable. The rough logic is as follows:

1
2
3
4
5
6
7
8
9
10
ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
v2 := nil
for ; hv1 < hn; hv1++ {
tmp := ha[hv1]
v1, v2 = hv1, tmp

}

And when the Go version is greater than or equal to 1.22, this easily error-prone issue has been “fixed”, with the output result as follows:

1
2
3
4
5
6
7
8
9
10
// pointer example output
[0xc000010030 0xc000010048 0xc000010060]
&{wesley 20}
&{wei 30}
&{wesleywei 40}

// Closure example output:
0
1
2

Specifically, please refer to Fixing For Loops in Go 1.22 - The Go Programming Language. The corresponding relevant logic should be located around GitHub.

1.4 Special Usage of range

The for range loop can safely delete elements from a map when iterating over it in Go. This feature is due to the implementation of range and map. In many other programming languages, similar operations may cause iterators to become invalid, throw exceptions, or raise undefined behavior.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
d := map[string]string{
"wesley": "wei",
"medium": "blog",
}
for k := range d {
if k == "wesley" {
delete(d, k)
}
}
fmt.Println(d)
}

// output
map[medium:blog]
  1. In Python, when you try to delete an element from a dictionary (similar to Go’s map) while iterating over it, you will usually get a RuntimeError, because Python’s iterator mechanism does not allow modifying the dictionary during iteration.
  2. In Java, using an Iterator to iterate over a HashMap allows you to remove the current element using the remove() method. However, if you directly call the Map‘s remove() method without using an Iterator, you will get a ConcurrentModificationException.

These two languages want to implement the logic for deleting elements in a map, which is slightly more complicated:

1
2
3
4
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in list(my_dict.keys()):
if some_condition(key):
del my_dict[key]
1
2
3
4
5
6
7
8
Map<String, Integer> map = new HashMap<>();
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
if (someCondition(entry)) {
iterator.remove();
}
}

Since range allows deleting elements, adding new elements using the same mechanism is also possible.

Combining the randomness of a map hash table, what will be the output of the following example? Will it go into an infinite loop or not?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
var addTomap = func() {
var t = map[string]string{
"wesley": "handsome",
"wesley1": "handsome1",
"wesley2": "handsome2",
}
for k := range t {
t[k+"wesley3"] = "handsome3"
fmt.Printf("%s-%s ", k, t[k])
}
}
for i := 0; i < 10; i++ {
addTomap()
fmt.Println()
}
}

References

  1. Redefining for loop variable semantics · golang/go · Discussion #56010 · GitHub
  2. Go Range Loop Internals

More Series Articles about You Should Know In Golang:

https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a

And I’m Wesley, delighted to share knowledge from the world of programming. 

Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. it would be a great help to me.

Give me some free applauds, highlights, or replies, and I’ll pay attention to those reactions, which will determine whether or not I continue to post this type of article.

See you in the next article. 👋

中文文章: https://programmerscareer.com/zh-cn/golang-range/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: Originally written at https://programmerscareer.com/golang-range/ at 2024-09-01 19:05. If you choose to repost or use this article, please cite the original source.

Collections in Go Collecting Information and Building a Personal Inspiration Library

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×