As we all know, we usually has two ways to iterate over a collection in Go: traditional for loop and for range loop. Here is a question, if we add new elements to the collection during the iteration, what will happen? is it different between the two iteration methods?
Let’s take a look at the following two examples:
for range loop
func main() {
s := []int{1, 2, 3}
for i, v := range s {
if i == 1 {
s = append(s, 4)
}
fmt.Println(i, v)
}
}
Output:
0 1
1 2
2 3
traditional for loop
func main() {
s := []int{1, 2, 3}
for i := 0; i < len(s); i++ {
if i == 1 {
s = append(s, 4)
}
fmt.Println(i, s[i])
}
}
Output:
0 1
1 2
2 3
3 4
As we can see, when we use the for range loop, it only iterates over the original elements of the slice. But when we use the traditional for loop, it can iterate over the newly added elements as well.
Why is that? Let’s dive into the implementation details of the for range loop.
How does Go implement for range loop?
When we use the for range loop, Go will translate it into normal for loop with an iterator, but the details depend on the type of the collection we are iterating over.
Slice
When we use for range loop to iterate over a slice, Go will translate it into normal for loop like below:
for i, v := range slice {
// ...
}
converts to
{
slice_tmp := slice
len_tmp := len(slice_tmp)
for i_tmp := 0; i_tmp < len_tmp; i_tmp++ {
i := i_tmp
v := slice_tmp[i_tmp]
// ...
}
}
As we can see, Go will create a temporary variable slice_tmp to hold the original slice, and it’s kind of like a snapshot of the slice at the moment when the for range loop starts.
Then it will use the length of this temporary
So here is another question, what if we modify the elements of the slice during the iteration? Will it be reflected in the iteration?
From above code, we can foresee if we modify the elements which haven’t been iterated yet, it will be reflected in the iteration.
for loop
func main() {
s := []int{1, 2, 3}
for i := 0; i < len(s); i++ {
if i == 1 {
s[2] = 4
}
fmt.Println(i, s[i])
}
}
Output:
0 1
1 2
2 4
for range loop
func main() {
s := []int{1, 2, 3}
for i, v := range s {
if i == 1 {
s[2] = 4
}
fmt.Println(i, v)
}
}
Output:
0 1
1 2
2 4
Map
When we use for range loop to iterate over a map, Go will translate it into below code:
for k, v := range m {
// ...
}
{
it := mapiterinit(typeof(m), m)
for ; it.key != nil; mapiternext(it) {
k := *(*keyType)(it.key)
v := *(*valueType)(it.value)
// ...
}
}
Channel
When we use for range loop to iterate over a channel, Go will translate it into below code:
for v := range ch {
// ...
}
{
for {
v, ok := <-ch
if !ok {
break
}
// ...
}
}
String
When we use for range loop to iterate over a string, Go will translate it into below code:
for i, r := range str {
// ...
}
{
str_tmp := str
for i_tmp := 0; i_tmp < len(str_tmp); {
r := rune(str_tmp[i_tmp])
if r < 0x80 { // ASCII character
i := i_tmp
i_tmp++
} else { // Multi-byte character
r, size := utf8.DecodeRuneInString(str_tmp[i_tmp:])
i := i_tmp
i_tmp += size
}
// ...
}
}