Go - Loops
Go has only 1 looping construct which is the for
loop.
The for
statement specifies repeated execution of a block.
For Loop Components
The for
loop has 3 components separated by semicolons:
The init statement:
- Executed once before evaluating the condition for the first iteration
- May be a short variable declaration
- Variables declared by the init statement are re-used in each iteration
- The declared variables are only visible in the scope of the for loop
The condition expression:
- Evaluated before each iteration
- The block is executed as long as a boolean condition evaluates to
true
- If absent, it is equivalent to the boolean value
true
The post statement:
- Executed after each execution of the block
- An increment or decrement statement
for init; condition; post {
// ...
}
For example:
for i := 0; i < 10; i++ {
// Print 0 to 9 each on a new line.
fmt.Println(i)
}
Single Condition
The for
loop can be used with only the condition expression which makes it act like a while
loop in other languages.
i := 1
// Repeat the execution as long as the condition evaluates to `true`.
for i < 100 {
i += i
}
Infinite Loop
If the condition is absent, it is equivalent to the boolean value true
which creates an infinite loop.
// Infinite loop.
for {
// Do something.
if someCondition {
// The break statement can be used to exit the loop
// when a certain condition is met.
break
}
}
// Same as
for true {
// ...
}
Range Loop
The for range
loop can be used to iterate over arrays, slices, strings, maps or channel values.
The range
expression is evaluated once before beginning the loop, it generates 2 iteration variables, one for the index and the second for the value, except for channels only 1 variable with the value is generated.
To omit the index, the blank identifier _
may be used in place of the index variable.
Array or pointer to an array:
arr := [5]string{"a", "b", "c", "d", "e"}
// The first iteration variable is the index (starting at 0).
// The second iteration variable is the value.
for i, v := range arr {
fmt.Println(i, v)
}
// Using a pointer to an array.
// Omit the index using the blank identifier.
for _, v := range &arr {
fmt.Println(i, v)
}
Slice:
slice := []string{"a", "b", "c", "d", "e"}
// The first iteration variable is the index (starting at 0).
// The second iteration variable is the value.
for i, v := range slice {
fmt.Println(i, v)
}
String:
str := "Hello, World"
// The first iteration variable is the byte index (starting at 0).
// The second iteration variable is the Unicode code point (rune type).
// If the iteration encounters an invalid UTF-8 sequence
// the second value will be 0xFFFD (Unicode replacement character).
for i, r := range str {
fmt.Println(i, r, string(r))
}
Map:
m := map[string]int{"a": 1, "b": 2, "c": 3}
// The iteration order over maps is not guaranteed.
// The first iteration variable is the key.
// The second iteration variable is the value.
for k, v := range m {
fmt.Println(k, v)
}
Channel:
var ch chan int = producer()
// For channels there's only 1 iteration variable, it will hold
// the successive values sent on the channel until the channel is closed.
for i := range ch {
fmt.Println(i)
}
// Empty a channel.
for range ch {}
Break Out of a Loop
The break
statement can be used to break out of a loop early.
for i := 0; i < 10; i++ {
if i == 5 {
break
}
// Only 0 to 4 will be printed.
fmt.Println(i)
}
To break out of nested loops, a label can be used:
package main
import "fmt"
func main() {
arr := [][]string{
{"a", "b", "c", "d", "e"},
{"f", "g", "h", "i", "j"},
{"k", "l", "m", "n", "o"},
{"p", "q", "r", "s", "t"},
{"u", "v", "w", "x", "y"},
{"z"},
}
letterToFind := "m"
outer:
for i := 0; i < len(arr); i++ {
// Loop through the nested slice elements.
for j := 0; j < len(arr[i]); j++ {
// Check if the current letter is the one we're searching for.
if arr[i][j] == letterToFind {
fmt.Printf("The letter was found at arr[%d][%d]\n", i, j)
break outer
}
}
}
}
Skip The Current Iteration
The continue
statement skips the current iteration of the loop and moves to the next.
for i := 0; i < 10; i++ {
if i%2 == 0 {
// Skip even numbers (move on to the next iteration).
continue
}
// Only 1, 3, 5, 7, 9 are printed.
fmt.Println(i)
}
The continue
statement can also be used with a label:
RowLoop:
for y, row := range rows {
// Inner loop.
for x, data := range row {
if data == endOfRow {
// Continue the execution of the outer loop.
continue RowLoop
}
}
// Do something.
}
Iteration Variable Capture
Anonymous functions created inside for
loops that reference the iteration variable and have their execution delayed will capture and share the same variable.
By the time these functions are called, the for
loop has been completed and the variable has been updated several times and will hold the value from the final iteration.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
// Delay the execution of the anonymous functions.
defer func() {
fmt.Println(i) // All the functions will print the last value `10`
}()
}
}
Workarounds
Create a new variable inside the loop:
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
// Create a new variable that holds the iteration variable's value.
// If the same variable name is used, it will shadow the outer variable.
i := i // Capture the current value in a new variable
defer func() {
fmt.Println(i) // Each function will print the correct value
}()
}
}
Pass the iteration variable as an argument to the closure:
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
// The function parameter can have the same variable name
// which shadows the iteration variable.
defer func(i int) {
fmt.Println(i) // Each function will print the correct value
}(i) // Pass the variable as an argument
}
}