One loop to rule them all
PHP has for, while, do … while and foreach, and that's before array_walk, array_map and the Collection pipeline you actually use. Go has for. Just for, in a few shapes. Once you've seen them, there's nothing else to learn about looping.
The shapes of for
The classic three-part loop is PHP's, minus the brackets and dollar signs:
for i := 1; i <= 3; i++ { fmt.Printf("Carriage %d\n", i) }
i is scoped to the loop, like the short statement in an if (1.8). One small difference: i++ is a statement in Go, not an expression, so x = i++ and $a[$i++]-style tricks don't compile.
Condition only is a while loop:
for seats > 0 { seats-- }
No condition at all loops until something calls break or return. Go has no do-while, so this is how you write one: loop forever, and check at the bottom.
for { line = readLine() if line == "" { break } }
break and continue work exactly as they do in PHP.
range
range is foreach. It walks a slice (Go's list, Unit 2), a string, a map, or even a number, and hands you two things each time round:
for i, stop := range stops { fmt.Printf("%d: %s\n", i, stop) }
For a slice, that's the index and the value, always in that order. Skip the index with _, as in for _, stop := range stops, and skip the value by leaving it off: for i := range stops.
Mind the gap
One variable means the index, not the value. PHP's foreach ($stops as $stop) gives you the values. Go's for stop := range stops gives you 0, 1, 2, because range always gives the index first. It compiles, it runs, and stop is an int. When you want the values, write for _, stop := range stops.
The value is also a copy. Assigning to stop inside the loop changes nothing in stops, just like PHP's foreach without &. To change the slice, assign through the index: stops[i] = strings.ToUpper(stops[i]). range has no by-reference form like &$stop, which also means no PHP-style "forgot to unset() the reference" bug.
Ranging over a number counts from zero: for i := range 3 gives 0, 1 and 2. It's the neatest way to say "do this n times", as long as you remember it starts at 0 and stops before n.
Ranging over a string gives you runes, as promised in 1.5. The index is the byte position where each rune starts, so range "Köln" gives positions 0, 1, 3 and 4. The ö takes two bytes.
Ranging over a map works too, but in random order on purpose. That's lesson 2.5, and it surprises everyone who's used PHP's ordered arrays.
Laravel corner
Most Laravel code barely writes foreach. It's collect($stops)->filter(…)->map(…)->values(). Go has no Collection pipeline: a plain for … range is the idiomatic answer, and the slices and maps packages (2.6) cover sorting, searching and friends. It feels like a step backwards for about a week. Then you notice you can read any loop without remembering what ->pluck() returns.
Breaking out of nested loops
PHP's break 2 counts levels. Go gives the outer loop a label and breaks out of it by name:
search: for _, line := range lines { for _, station := range line.Stations { if station == target { break search } } }
continue search works the same way. It's clearer than counting, and adding a third loop level later doesn't silently change what break 2 means.
Recap
foris the only loop: three-part, condition only (while), or bare (forever, untilbreak). There's no do-while.rangeisforeach. It gives index then value,_skips the index, and one variable means the index only.- The range value is a copy. Change the slice with
xs[i] = …. range ncounts 0 to n-1.rangeover a string gives runes, with byte positions.- Label the outer loop and
break labelinstead ofbreak 2.