for-range
"Do this for each element, one by one." That is what for ... range expresses, without the bookkeeping of an index variable and a length check. In this lesson, we will use it with strings and slices. Go also supports range on arrays, maps, and channels, which you will meet later.
Ranging over a string
A for ... range over a string yields pairs of byte offset and rune. Each iteration moves forward by as many bytes as the current rune occupies in UTF-8:
for i, r := range "héllo" {
fmt.Println(i, r, string(r))
}
Running that prints:
0 104 h
1 233 é
3 108 l <- notice the jump from 1 to 3
4 108 l
5 111 o
i is not the rune position. It is the offset into the underlying bytes of the string. After é (two bytes in UTF-8), the next index jumps from 1 to 3, skipping the second byte of é's encoding. If you want rune-by-rune access to text that might contain non-ASCII, range over a string is usually the right tool.
string(r) converts the rune back to a one-rune string for printing, which is the same conversion you met in the "Type conversions" lesson.
Ranging over a slice
A slice is an ordered sequence of values, roughly what other languages call a list or a resizable array. Slices get a full chapter of their own next, but you do not need the full story yet. The literal form []T{value1, value2, ...} creates one on the spot, and you can print it directly to see what it looks like:
nums := []int{10, 20, 30}
fmt.Println(nums) // [10 20 30]
That is a three-element slice of int. Now range over it:
for i, v := range nums {
fmt.Println(i, v)
}
// 0 10
// 1 20
// 2 30
Each iteration gives you two values: i, the integer index starting at zero, and v, a copy of the value at that index. That "index and value" pair is the default shape of range over any indexed collection (slices, arrays, and also strings, as you saw above).
Ignoring pieces with _
You will often only want one of the two values range produces. That is where Go's blank identifier comes in.
Remember the rule from the Variables lesson: declaring a variable you never use is a compile error. Range loops are not exempt. If you write both i and v but only use v, Go refuses to compile:
for i, v := range nums {
fmt.Println(v) // error: i declared and not used
}
You have two ways to fix this.
Drop the variable you do not want. If the index is all you need, use the single-variable form:
for i := range nums {
fmt.Println(i)
}
// 0
// 1
// 2
A single name after for gets you only the index. There is no single-variable form that gives you just the value, because Go always hands the index out first.
Use _ for positions you want to discard. The blank identifier is Go's formal way of saying "give me a slot here so I can keep the next one, but I do not need this value, so do not complain":
for _, v := range nums {
fmt.Println(v)
}
// 10
// 20
// 30
That is where _ earns its keep. When you want the value without the index, _ holds the index slot open so the rest of the destructuring works. You will see the same blank identifier used in other destructuring contexts later, like multiple-return functions where you want one return value but not the others.
range for string iterationIf you write the following code
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
and use s[i] to look at each position, you get bytes, not runes. That is fine for ASCII-only text but breaks once a non-ASCII character appears. for _, r := range s is the form that decodes UTF-8 for you and is what you should reach for unless you specifically need raw bytes.
rangerange iterates over a map too, yielding key-value pairs (for k, v := range m { ... }). Map iteration has its own nuances (keys come out in an unspecified order that changes between runs), so it gets a dedicated treatment in the Collections chapter.
The starter has two values: a string greeting := "hello" and a slice nums := []int{2, 4, 6, 8}.
- Use
rangeovergreetingto print each rune as a string (usestring(r)) on its own line. - Use
rangeovernumswith_in place of the index. Add each value to a runningsumvariable, and print the total after the loop.
h e l l o 20