Golang Range Traversal Issue

Golang Range Traversal Issue

In this article, we will delve into the intricacies of the range property in Golang and its implications when traversing arrays, slices, strings, maps, and other structures. We will explore a bug that arose in a recent project due to improper use of the range property and discuss the solution to this issue.

Range Property

The range property in Golang provides a simple and fast way to traverse arrays, slices, strings, maps, and other structures. However, when using it, we must pay attention to the difference between the value of the pointer and copying the copy.

A Recent Project’s Bug

In a recent project, we encountered a bug due to the improper use of the range property. The code snippet below illustrates the issue:

func (rs *RouterSwapper) Use(mwf ...mux.MiddlewareFunc) {
    for _, m := range mwf {
        rs.middlewares = append(rs.middlewares, &m)
    }
}

func (rs *RouterSwapper) Swap(newRouter *mux.Router) {
    for _, midleware := range rs.middlewares {
        newRouter.Use(*midleware)
    }
}

Here, the Use method loads middleware stored in the middlewares array, and the Swap method adds these middleware elements to a new router. However, we found that only one element was always added to the middlewares array, and the other elements were repeated three times.

Testing the Range Property

To better understand the issue, we wrote some test cases to demonstrate the usage of the range property:

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    for _, v := range arr {
        fmt.Println(v)
    }
}

This basic usage of the range property produces the expected output: 12345.

Next, we tried to print the address of each element:

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    for _, v := range arr {
        fmt.Println(&v)
    }
}

However, we observed that the output was always the same address: 0xc000054080. This is because the variable v is reused, but its address does not change.

To rule out the possibility that the array is a pointer type, we declared an array of pointers and traversed it:

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    arr2 := [5]*int{&arr[0], &arr[1], &arr[2], &arr[3], &arr[4]}
    for _, v := range arr2 {
        fmt.Println(v)
    }
}

The output was not a problem, and we saw different addresses for each element.

Conclusion

The project bug was due to the improper use of the range property in the Use method, which always added the last element of the middlewares array. To solve this issue, we replaced the middlewares array with instance types:

middlewares []mux.MiddlewareFunc

However, this approach had its own set of problems, as it created unnecessary instance creation and impacted the speed and GC of the program.

Solution

The correct approach is to use the first method and avoid creating local variables. We can use the for i, _ := range arr syntax to replace for i, v := range arr and avoid creating local variables.

Range Property Characteristics

According to the Golang official documentation, the range property has the following characteristics:

Range expression:
1st value: 2nd value
array or slice: nE, *nE, or []E
index: i int
a: string type: index: i int
map: m: mapKV: key: k K, value: v V
channel: c: chan E, <-chan E: element: e E

The iteration variables may be declared by the “range” clause using a form of short variable declaration (:=). In this case, their types are set to the types of the respective iteration values, and their scope is the block of the “for” statement.

Solution to the Bug

To solve the bug, we can use the index to values:

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    for i, _ := range arr {
        fmt.Println(&arr[i])
    }
}

This produces the correct output:

0xc00000e2a00
0xc00000e2a80
0xc00000e2b00
0xc00000e2b80
0xc00000e2c0

Alternatively, we can create a new variable each time and print its address:

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    for _, v := range arr {
        a := v
        fmt.Println(&a)
    }
}

However, this approach has several problems, including the creation of unnecessary instances and the impact on the speed and GC of the program.