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
}
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)
}
}
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)
}
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))
}
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)
}
}
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)
}
}
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])
}
}
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...")
}
}
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
}
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.