Some tricks and tips for using for range in GoLang

  sonic0002        2020-03-08 01:07:00       29,987        0    

GoLang provides two major ways to loop through elements of array, slice and map. They are for and for range. Many people find that for range is very convenient when don't care about the index of the element. In this post, some tricks and tips would be talked about regarding for range.

1. Loop and get pointer of each element

Assume there is a code snippet like below which is to get the pointer of each element in an array and create a new array with the corresponding pointer.

arr := [2]int{1, 2}
res := []*int{}
for _, v := range arr {
    res = append(res, &v)
}
//expect: 1 2
fmt.Println(*res[0],*res[1])
//but output: 2 2

As above result shows, the output is not expected. It only prints the last element. What is the problem? This same problem applies to slice and map as well.

If checking GoLang source code, it is easy to find that for range is actually a syntax sugar for the for loop.  Internally it still uses a for loop and it will first copy all the elements of the array and loop through them and assign each index and value to a temp variable. Hence when retrieving the address of the value, it actually takes the address of the temp variable and the new array would actually has every element points to the same address.

// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
//     value_temp = range_temp[index_temp]
//     index = index_temp
//     value = value_temp
//     original body
//   }

How to fix this problem? Two possible ways.

Use a local variable

for _, v := range arr {
    // the value of v is assigned to a new local variable v
    v := v
    res = append(res, &v)
}

Use index to get the element

for k := range arr {
    res = append(res, &arr[k])
}

2. Will the loop run infinitely?

Assume there is below code snippet which loops an array where the array changes within the loop itself

v := []int{1, 2, 3}
for i := range v {
    v = append(v, i)
}

The answer is no. As explained earlier, when the loop begins, it will copy the original array to a new one and loop through the elements, hence when appending elements to the original array, the copied array actually doesn't change.

3. Issue with loop through large array

var arr = [102400]int{1, 1, 1}
for i, n := range arr {
    //just ignore i and n for simplify the example
    _ = i
    _ = n
}

The problem with this is that it will raise memory consumption concern as it will first copy all the elements to a new array which takes same space as the original array. 

To resolve this kind of concern, can follow below two ways.

// take address of the original array and loop
for i, n := range &arr

// create a slice based on original array
for i, n := range arr[:]

4. Is it efficient to reset a large array with for range?

var arr = [102400]int{1, 1, 1}
for i, _ := range &arr {
    arr[i] = 0
}

The answer is yes as the GoLang has some optimization on resetting value to default value per source code.

// Lower n into runtime·memclr if possible, for
// fast zeroing of slices and arrays (issue 5373).
// Look for instances of
//
// for i := range a {
// 	a[i] = zero
// }
//
// in which the evaluation of a is side-effect-free.

5. Can access deleted element in a map within the loop?

var m = map[int]int{1: 1, 2: 2, 3: 3}
//only del key once, and not del the current iteration key
var o sync.Once
for i := range m {
    o.Do(func() {
        for _, key := range []int{1, 2, 3} {
            if key != i {
                fmt.Printf("when iteration key %d, del key %d\n", i, key)
                delete(m, key)
                break
            }
        }
    })
    fmt.Printf("%d%d ", i, m[i])
}

No, probably not.  Map internally stores elements in a linked list, to ensure that it's random, the element it starts to loop is randomly selected. If the element deleted is not in the already looped element list, the element will never be accessed again. Otherwise, it is still accessible.

6. Is it OK to create goroutine in the loop?

var m = []int{1, 2, 3}
for i := range m {
    go func() {
        fmt.Print(i)
    }()
}
//block main 1ms to wait goroutine finished
time.Sleep(time.Millisecond)

The answer is no. It has the same issue as the first one mentioned above. To solve this, can follow below.

Pass as parameter

for i := range m {
    go func(i int) {
        fmt.Print(i)
    }(i)
}

Use local variable

for i := range m {
    i := i
    go func() {
        fmt.Print(i)
    }()
}

Reference: https://mp.weixin.qq.com/s?__biz=MzUxNzA2NzEzNw==&mid=2247483801&idx=1&sn=d4323d17c3b0c702ce50e8e6e0250f70&chksm=f99c9977ceeb10616498d7169162327011e5414129a8f00f594a9b395b008fe7b8b6ea065ef3

POINTER  FOR LOOP  GOLANG  FOR RANGE 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Software engineer estimation