What the for? One loop to rule them all!

Nicolas Lepage - Dec 31 '19 - - Dev Community

When I first learned that Go has only one keyword for loops, I thought to myself "What the f***? Aren't they oversimplifying things?".

Now, looking back, I think getting rid of while and do...while in favor of having only for, is a good choice.

I wouldn't say this is easier to learn for beginners. Replacing several keywords by several forms of the same keyword doesn't minimize what you have to learn as a total newbie.

However, having used Go for several years now, I feel comfortable with its for loop keyword. Writing it or reading it from other people's code always feels simple.

Now let's have a look at its different syntaxes.

Forever

Starting with my favorite one:

package main

import "fmt"

func main() {
    // Don't try this at home
    for {
        fmt.Println("To infinity and beyond!")
    }
    // This infinite loop prints a message to standard output
}

Run it on Go playground

Nice and pure, the infinite loop needs only the for keyword and its brackets.

This is compact, and in my opinion pretty intuitive.

Good ol' for

Of course there is the classic for, with an initialization, a condition, and an afterthought, separated by semicolons:

package main

import "fmt"

func main() {
    // Count to ten on standard output:
    for i := 1; i <= 10; i++ {
        fmt.Println(i)
    }
}

Run it on Go playground

Notice the absence of parentheses? Go doesn't have parentheses around statement's parameters.

Each part may be ommited:

package main

import "fmt"

// This function counts down from n to 1
func countDown(n int) {
    // No initialization is needed
    for ; n >= 1; n-- {
        fmt.Println(n)
    }
}

func main() {
    countDown(10)
}

Run it on Go playground

The two semicolons must be kept for the syntax to be valid (only one semicolon is forbidden).

You may omit all the parts, which is the equivalent of an infinite for loop.

For a while

If you need the equivalent of a while, that is a loop with only a condition, just use for like you would use a while:

package main

import "fmt"

// This function adds leading zeros to str
// in order ot reach a length of n
func padZeros(str string, n int) string {
    for len(str) < n {
        str = "0" + str
    }
    return str
}

func main() {
    // Prints "0000000123"
    fmt.Println(padZeros("123", 10))
}

Run it on Go playground

Not much to say, this is rather intuitive, still no parentheses.

for...range

A common use case is to iterate over a list of items.

This can be done using the range keyword in conjunction with for:

package main

import "fmt"

func main() {
    var messages = []string{"May", "the", "for", "be", "with", "you"}

    // Prints each message preceded by its index
    for i, message := range messages {
        fmt.Println(i, message)
    }
}

Run it on Go playground

As you can see, for...range allows to retrieve each index and its associated value.

range may also be used with maps, in which case it retrieves a key instead of an index.

If you need only the value, the index/key may be discarded using _:

package main

import "fmt"

func main() {
    var messages = []string{"May", "the", "for", "be", "with", "you"}

    // Prints each message
    for _, message := range messages {
        fmt.Println(message)
    }
}

Run it on Go playground

And in case you need only the index/key, the value may be ommited:

package main

import "fmt"

func main() {
    var messages = []string{"May", "the", "for", "be", "with", "you"}

    // Prints each message
    for i := range messages {
        fmt.Println(messages[i])
    }
}

Run it on Go playground

Finally, if you don't need the index/key or the value, both can be omitted:

package main

import "fmt"

func main() {
    var messages = []string{"May", "the", "for", "be", "with", "you"}

    // Allows you to iterate len(messages) times
    for range messages {
        fmt.Println("Do something...")
    }
}

Run it on Go playground

for...range with channels

Go is well known for its CSP concurrency model, which favors goroutines and channels over threads and mutexes.

for...range may also be used when receiving values from a channel:

package main

import "fmt"

func main() {
    ch := make(chan int) // Create a channel
    go send123(ch)       // Start send123() in a new goroutine

    // Receive and print integers from channel
    for i := range ch {
        fmt.Println(i)
    }
}

func send123(ch chan int) {
    // Send 3 integers through channel
    for i := 1; i <= 3; i++ {
        ch <- i
    }

    close(ch) // Close channel
}

Run it on Go playground

Used on a channel, for...range will wait for a value to be received at each iteration, blocking the goroutine which is executing it.

The loop ends when the channel is closed by the sending goroutine.

Conclusion

As you can see having only one keyword for loops doesn't change things much if you were used to C-style languages.

It makes for rather understandable code, which I think is one of the most important things if you have to work with a team.

I hope you enjoyed this quick introduction to Go's loops, give a ❤️, 💬 leave a comment, or share it with others, and follow me to get notified of my next posts.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .