Byte Ebi's Logo

Byte Ebi 🍀

A Bit everyday A Byte every week

[A Tour of Go Study Notes] 03 Flow Control

Understanding Go language flow control using the official tutorial

Ray

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.

Recent Posts

Categories

Tags