In this article by Vladimir Vivien author of the book Learning Go programming explains some basic control flow of Go programming language. Go borrows several of the control flow syntax from its C-family of languages.
It supports all of the expected control structures including if-else, switch, for-loop, and even goto. Conspicuously absent though are while or do-while statements.
The following topics examine Go's control flow elements. Some of which you may already be familiar and others that bring new set of functionalities not found in other languages.
(For more resources related to this topic, see here.)
The if-statement, in Go, borrows its basic structural form from other C-like languages. The statement conditionally executes a code-block when the Boolean expression that follows the if keyword which evaluates to true as illustrated in the following abbreviated program that displays information about the world currencies.
import "fmt"
type Currency struct {
Name string
Country string
Number int
}
var CAD = Currency{
Name: "Canadian Dollar",
Country: "Canada",
Number: 124}
var FJD = Currency{
Name: "Fiji Dollar",
Country: "Fiji",
Number: 242}
var JMD = Currency{
Name: "Jamaican Dollar",
Country: "Jamaica",
Number: 388}
var USD = Currency{
Name: "US Dollar",
Country: "USA",
Number: 840}
func main() {
num0 := 242
if num0 > 100 || num0 < 900 {
mt.Println("Currency: ", num0)
printCurr(num0)
} else {
fmt.Println("Currency unknown")
}
if num1 := 388; num1 > 100 || num1 < 900 {
fmt.Println("Currency:", num1)
printCurr(num1)
}
}
func printCurr(number int) {
if CAD.Number == number {
fmt.Printf("Found: %+vn", CAD)
} else if FJD.Number == number {
fmt.Printf("Found: %+vn", FJD)
} else if JMD.Number == number {
fmt.Printf("Found: %+vn", JMD)
} else if USD.Number == number {
fmt.Printf("Found: %+vn", USD)
} else {
fmt.Println("No currency found with number", number)
}
}
The if statement in Go looks similar to other languages. However, it sheds a few syntactic rules while enforcing new ones.
if (num0 > 100 || num0 < 900) {
fmt.Println("Currency: ", num0)
printCurr(num0)
}
Use instead:
if num0 > 100 || num0 < 900 {
fmt.Println("Currency: ", num0)
printCurr(num0)
}
if num0 > 100 || num0 < 900 printCurr(num0)
However, this will compile:
if num0 > 100 || num0 < 900 {printCurr(num0)}
if num0 > 100 || num0 < 900 {printCurr(num0)}
However, the preferred idiomatic layout for the statement is to use multiple lines as follows:
if num0 > 100 || num0 < 900 {
printCurr(num0)
}
if num0 > 100 || num0 < 900 {
fmt.Println("Currency: ", num0)
printCurr(num0)
} else {
fmt.Println("Currency unknown")
}
if CAD.Number == number {
fmt.Printf("Found: %+vn", CAD)
} else if FJD.Number == number {
fmt.Printf("Found: %+vn", FJD)
The if-else-if statement chain can grow as long as needed and may be terminated by an optional else statement to express all other untested conditions. Again, this is done in the printCurr() function which tests four conditions using the if-else-if blocks. Lastly, it includes an else statement block to catch any other untested conditions:
func printCurr(number int) {
if CAD.Number == number {
fmt.Printf("Found: %+vn", CAD)
} else if FJD.Number == number {
fmt.Printf("Found: %+vn", FJD)
} else if JMD.Number == number {
fmt.Printf("Found: %+vn", JMD)
} else if USD.Number == number {
fmt.Printf("Found: %+vn", USD)
} else {
fmt.Println("No currency found with number", number)
}
}
In Go, however, the idiomatic and cleaner way to write such a deep if-else-if code block is to use an expressionless switch statement. This is covered later in the section on SwitchStatement.
The if statement supports a composite syntax where the tested expression is preceded by an initialization statement. At runtime, the initialization is executed before the test expression is evaluated as illustrated in this code snippet (from the program listed earlier).
if num1 := 388; num1 > 100 || num1 < 900 {
fmt.Println("Currency:", num1)
printCurr(num1)
}
The initialization statement follows normal variable declaration and initialization rules. The scope of the initialized variables is bound to the if statement block beyond which they become unreachable. This is a commonly used idiom in Go and is supported in other flow control constructs covered in this article.
Go also supports a switch statement similarly to that found in other languages such as C or Java. The switch statement in Go achieves multi-way branching by evaluating values or expressions from case clauses as shown in the following abbreviated source code:
import "fmt"
type Curr struct {
Currency string
Name string
Country string
Number int
}
var currencies = []Curr{
Curr{"DZD", "Algerian Dinar", "Algeria", 12},
Curr{"AUD", "Australian Dollar", "Australia", 36},
Curr{"EUR", "Euro", "Belgium", 978},
Curr{"CLP", "Chilean Peso", "Chile", 152},
Curr{"EUR", "Euro", "Greece", 978},
Curr{"HTG", "Gourde", "Haiti", 332},
...
}
func isDollar(curr Curr) bool {
var bool result
switch curr {
default:
result = false
case Curr{"AUD", "Australian Dollar", "Australia", 36}:
result = true
case Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}:
result = true
case Curr{"USD", "US Dollar", "United States", 840}:
result = true
}
return result
}
func isDollar2(curr Curr) bool {
dollars := []Curr{currencies[2], currencies[6], currencies[9]}
switch curr {
default:
return false
case dollars[0]:
fallthrough
case dollars[1]:
fallthrough
case dollars[2]:
return true
}
return false
}
func isEuro(curr Curr) bool {
switch curr {
case currencies[2], currencies[4], currencies[10]:
return true
default:
return false
}
}
func main() {
curr := Curr{"EUR", "Euro", "Italy", 978}
if isDollar(curr) {
fmt.Printf("%+v is Dollar currencyn", curr)
} else if isEuro(curr) {
fmt.Printf("%+v is Euro currencyn", curr)
} else {
fmt.Println("Currency is not Dollar or Euro")
}
dol := Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}
if isDollar2(dol) {
fmt.Println("Dollar currency found:", dol)
}
}
The switch statement in Go has some interesting properties and rules that make it easy to use and reason about.
Expression switches are flexible and can be used in many contexts where control flow of a program needs to follow multiple path. An expression switch supports many attributes as outlined in the following bullets.
func isDollar(curr Curr) bool {
var bool result
switch curr {
default:
result = false
case Curr{"AUD", "Australian Dollar", "Australia", 36}:
result = true
case Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}:
result = true
case Curr{"USD", "US Dollar", "United States", 840}:
result = true
}
return result
}
func isEuro(curr Curr) bool {
switch curr {
case currencies[2], currencies[4], currencies[10]:
return true
default:
return false
}
}
func isEuro(curr Curr) bool {
if curr == currencies[2] || curr == currencies[4],
curr == currencies[10]{
return true
}else{
return false
}
}
There is no automatic fall through in Go's case clause as it does in the C or Java switch statements. Recall that a switch block that will exit after executing its first matching case. The code must explicitly place the fallthrough keyword, as the last statement in a case block, to force the execution flow to fall through the successive case block. The following code snippet shows a switch statement with a fallthrough in each case block.
func isDollar2(curr Curr) bool {
switch curr {
case Curr{"AUD", "Australian Dollar", "Australia", 36}:
fallthrough
case Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344}:
fallthrough
case Curr{"USD", "US Dollar", "United States", 840}:
return true
default:
return false
}
}
When a case is matched, the fallthrough statements cascade down to the first statement of the successive case block. So if curr = Curr{"AUD", "Australian Dollar", "Australia", 36}, the first case will be matched. Then the flow cascades down to the first statement of the second case block which is also a fallthrough statement. This causes the first statement, return true, of the third case block to execute. This is functionally equivalent to following snippet.
switch curr {
case Curr{"AUD", "Australian Dollar", "Australia", 36},
Curr{"HKD", "Hong Kong Dollar", "Hong Koong", 344},
Curr{"USD", "US Dollar", "United States", 840}:
return true
default:
return false
}
Go supports a form of the switch statement that does not specify an expression. In this format, each case expression must evaluate to a Boolean value true. The following abbreviated source code illustrates the uses of an expressionless switch statement as listed in function find(). The function loops through the slice of Curr values to search for a match based on field values in the struct passed in:
import (
"fmt"
"strings"
)
type Curr struct {
Currency string
Name string
Country string
Number int
}
var currencies = []Curr{
Curr{"DZD", "Algerian Dinar", "Algeria", 12},
Curr{"AUD", "Australian Dollar", "Australia", 36},
Curr{"EUR", "Euro", "Belgium", 978},
Curr{"CLP", "Chilean Peso", "Chile", 152},
...
}
func find(name string) {
for i := 0; i < 10; i++ {
c := currencies[i]
switch {
case strings.Contains(c.Currency, name),
strings.Contains(c.Name, name),
strings.Contains(c.Country, name):
fmt.Println("Found", c)
}
}
}
Notice in the previous example, the switch statement in function find() does not include an expression. Each case expression is separated by a comma and must be evaluated to a Boolean value with an implied OR operator between each case. The previous switch statement is equivalent to the following use of if statement to achieve the same logic.
func find(name string) {
for i := 0; i < 10; i++ {
c := currencies[i]
if strings.Contains(c.Currency, name) ||
strings.Contains(c.Name, name) ||
strings.Contains(c.Country, name){
fmt.Println("Found", c)
}
}
}
The switch keyword may be immediately followed by a simple initialization statement where variables, local to the switch code block, may be declared and initialized. This convenient syntax uses a semicolon between the initializer statement and the switch expression to declare variables which may appear anywhere in the switch code block. The following code sample shows how this is done by initializing two variables name and curr as part of the switch declaration.
func assertEuro(c Curr) bool {
switch name, curr := "Euro", "EUR"; {
case c.Name == name:
return true
case c.Currency == curr:
return true
}
return false
}
The previous code snippet uses an expressionless switch statement with an initializer. Notice the trailing semicolon to indicate the separation between the initialization statement and the expression area for the switch. In the example, however, the switch expression is empty.
Given Go's strong type support, it should be of little surprise that the language supports the ability to query type information. The type switch is a statement that uses the Go interface type to compare underlying type information of values (or expressions). A full discussion on interface types and type assertion is beyond the scope of this section.
For now all you need to know is that Go offers the type interface{}, or empty interface, as a super type that is implemented by all other types in the type system. When a value is assigned type interface{}, it can be queried using the type switch as, shown in function findAny() in following code snippet, to query information about its underlying type.
func find(name string) {
for i := 0; i < 10; i++ {
c := currencies[i]
switch {
case strings.Contains(c.Currency, name),
strings.Contains(c.Name, name),
strings.Contains(c.Country, name):
fmt.Println("Found", c)
}
}
}
func findNumber(num int) {
for _, curr := range currencies {
if curr.Number == num {
fmt.Println("Found", curr)
}
}
}
func findAny(val interface{}) {
switch i := val.(type) {
case int:
findNumber(i)
case string:
find(i)
default:
fmt.Printf("Unable to search with type %Tn", val)
}
}
func main() {
findAny("Peso")
findAny(404)
findAny(978)
findAny(false)
}
The function findAny() takes an interface{} as its parameter. The type switch is used to determine the underlying type and value of the variable val using the type assertion expression:
switch i := val.(type)
Notice the use of the keyword type in the type assertion expression. Each case clause will be tested against the type information queried from val.(type). Variable i will be assigned the actual value of the underlying type and is used to invoke a function with the respective value. The default block is invoked to guard against any unexpected type assigned to the parameter val parameter. Function findAny may then be invoked with values of diverse types, as shown in the following code snippet.
findAny("Peso")
findAny(404)
findAny(978)
findAny(false)
This article gave a walkthrough of the mechanism of control flow in Go including if, switch statements. While Go’s flow control constructs appear simple and easy to use, they are powerful and implement all branching primitives expected for a modern language.
Further resources on this subject: