Go's range clause is powerful, but requires knowledge of the rest of Go to be used properly.

Go's language specification is remarkably easy to read, and the part concerning range clauses is reasonably short. However, unless you're familiar with other aspects of the language, range may surprise you. Since they touch so many other areas of Go, range clauses make a good test of your understanding. All examples have links to the playground where you can run them.

To summarise (and omit some details):

The expression on the RHS is evaluated once, at the beginning.

The variables on the LHS are assigned once per iteration.

The second rule (2) tells us that if we modify the iteration variables (most commonly denoted by i and v, for index and value), then any changes will be ignored and overwritten when the next iteration starts. We can't skip over values using a range clause:

Slices

Understanding slices is essential to writing idiomatic Go. Slices act like references to the underlying array, so when the expression on the RHS of the range clause is evaluated at the start (1), its length is fixed for the whole loop. If we reslice within the loop, the RHS is not re-evaluated (1). i and v are assigned their values as normal (2), but trying to access slice[i] will cause a panic.

Arrays

Arrays (unlike slices) are values (as opposed to pointers). That means no two arrays use the same memory, and assignment involves a copy. When we evaluate the array at the beginning of the loop (1), we create a copy - so modifications to "the array" are not reflected in the iteration variables, so v has the value of a[i] in the original array.

var a = [4]string{"north", "east", "south", "west"}
for i, v := range a {
//modifying an array will cause a[i] to change
//but v will refer to the value of a[i] when the
//loop started
fmt.Println(i, a[i], v)
if i != len(a)-1 {
a[i+1] = strings.ToUpper(a[i+1])
}
}

If you don't want to create a copy of an array when you loop over it, you can loop over a pointer to an array. I'm not sure what advantage there is over slicing the array - they appear to be equivalent.

Note that in some cases, v != greeting[i]. In fact, they are of different types, so that test wouldn't even compile! v contains successive Unicode code points, and has type rune. i is the byte position of that code point in the string, and greeting[i] is the byte at that point. So i does not increase by one each time, as Unicode code points can be more than one byte long. greeting[i] may just take the first byte of each Unicode code point, and when displayed as a character (using "%c"), shows a different character to one you might expect.

Strings in Go are immutable, so there are no factors to consider about modification when ranging.

Maps

Maps are widely used in Go, and have been given a prominent position in the language. Personally, I find this makes me a lot more inclined to use them than in other languages, resulting in simpler code.

Quiz

Conclusion

Go's range clause is both useful and widely used. Go is a small, simple language - and I think that is its greatest feature. Using range properly requires a good knowledge of the aspect of the language that you're using it in - but it is consistent and flexible.