[A Tour of Go Study Notes] 03 Flow Control
Understanding Go language flow control using the official tutorial
Entering the world of Golang, we first use the official tutorial: A Tour of Go
to learn the basics of using Golang.
This article is a note on flow control, explaining some concepts like if, else, for, switch, and defer.
for
Go has only one type of loop: for
.
Its structure consists of:
- Initialization: Executed before the first iteration.
- Condition: Evaluated before each iteration.
- Post statement: Executed after each iteration.
The initialization is often a short variable declaration that exists only within the for
loop.
Once the condition evaluates to false
, the loop stops.
Here’s a basic loop example:
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
/*
>>> 45
*/
Initialization and post statement can be omitted:
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
/*
>>> 1024
*/
You can even omit everything, creating a while
loop-like construct:
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
/*
>>> 1024
*/
For an infinite loop, just omit the condition, and the loop will not end. This makes it easy to create an infinite loop:
package main
func main() {
for {
}
}
if
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
/*
>>> 1.4142135623730951 2i
*/
Short Statement
Similar to for
, if
has a short statement form, where a simple operation can be executed before the condition.
Refer to the example below. Note that the variable v
is scoped only within the if
block:
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
// v cannot be used here
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
/*
>>> 9 20
*/
else
Variables declared in if
can also be used in the corresponding else
block:
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// v cannot be used here
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
/*
>>> 27 >= 20
>>> 9 20
*/
switch
One notable difference in Go’s switch
compared to other languages is that it doesn’t require a break
statement.
It executes the corresponding case
, in some languages, it would continue executing subsequent case
statements.
You can use default
at the end to define the default case when none of the conditions match.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
}
/*
>>> Go runs on Linux.
*/
case
doesn’t necessarily need to be constants or integers; it can even execute calculations:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}
/*
>>> When's Saturday?
>>> Too far away.
*/
A switch without a condition is equivalent to switch true
, making it more readable than a long if-then-else chain.
It matches the first case that evaluates to true, executing its block:
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
/*
>>> Good evening.
*/
defer
defer
postpones the execution of a function until the surrounding function returns.
However, the arguments are evaluated immediately, and the function is executed after the outer function completes.
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
/*
>>> hello
>>> world
*/
Stacking defers
What happens when you use defer
in a loop?
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
/*
counting
done
9
8
7
6
5
4
3
2
1
0
*/
First, notice that the two Println
outside the loop are executed first.
This is easy to understand. Then, enter the loop, iterate from 0 to 9, and each current number is deferred to be executed later.
The actual output sequence is from 9 to 0, so you can deduce that defer
is a form of stacking. When multiple defer
statements coexist, they follow the Last In, First Out (LIFO) order for execution.