3. Control Structures

Science is what we understand well enough to explain to a computer. Art is everything else we do. — Donald E. Knuth

Control structure determines how the code block will get executed for the given conditional expressions and parameters. Go provides a number of control structures including for, if, switch, defer, and goto. The Quick Start chapter has already introduced control structures like if and for. This chapter will elaborate more about these topics and introduce some other related topics.

3.1. If

3.1.1. Basic If

Programming involves lots of decision making based on the input parameters. The If control structure allows to perform a particular action on certain condition. A conditional expressions is what used for making decisions in the code using the If control structure. So, the If control structure will be always associated with a conditional expression which evaluates to a boolean value. If the boolean value is true, the statements within the block will be executed. Consider this example:

package main

import "fmt"

func main() {
    if 1 < 2 {
        fmt.Println("1 is less than 2")
     }
}

The first line starts with the if keyword followed by a conditional expression and the line ends with an opening curly bracket. If the conditional expression is getting evaluated as true, the statements given within the curly brace will get evaluated. In the above example, the conditional expression 1 < 2 will be evaluated as true so the print statement given below that will get executed. In fact, you can add any number of statements within the braces.

3.1.2. If and Else

Sometimes you need to perform different set of action if the condition is not true. Go provides a variation of the if syntax with the else block for that.

Consider this example with else block:

package main

import "fmt"

func main() {
    if 3 > 4 {
        fmt.Println("3 is greater than 4")
    } else {
        fmt.Println("3 is not greater than 4")
    }
}

In the above code, the code will be evaluated as false. So, the statements within else block will get executed.

3.1.3. Example

Now we will go through a complete example, the task is to identify the given number is even or odd. The input can be given as a command line argument.

package main

import (
     "fmt"
     "os"
     "strconv"
)

func main() {
     i := os.Args[1]
     n, err := strconv.Atoi(i)
     if err != nil {
             fmt.Println("Not a number:", i)
             os.Exit(1)
     }
     if n%2 == 0 {
             fmt.Printf("%d is even\n", n)
     } else {
             fmt.Printf("%d is odd\n", n)
     }
}

The os package has an attribute named Args. The value of Args will be a slice of strings which contains all command line arguments passed while running the program. As we have learned from the Quick Start chapter, the values can be accessed using the index syntax. The value at zero index will be the program name itself and the value at 1st index the first argument and the value at 2nd index the second argument and so on. Since we are expecting only one argument, you can access it using the 1st index (os.Args[1]).

The strconv package provides a function named Atoi to convert strings to integers. This function return two values, the first one is the integer value and the second one is the error value. If there is no error during convertion, the error value will be nil. If it’s a non-nil value, that indicates there is an error occured during conversion.

The nil is an identifier in Go which represents the “zero value” for certain built-in types. The nil is used as the zero for these types: interfaces, functions, pointers, maps, slices, and channels.

In the above example, the second expected value is an object conforming to built-in error interface. We will discuss more about errors and interfaces in later chapters. The zero value for interface, that is nil is considered as there is no error.

The Exit function within os package helps to exit the program prematurely. The argument passed will be exit status code. Normally exit code 0 is treated as success and non-zero value as error.

The conditional expression use the modulus operator to get remainder and checking it is zero. If the remainder against 2 is zero, the value will be even otherwise the value will odd.

3.1.4. Else If

There is a third alternative syntax available for the If control structure, that is else if block. The Else If block get executed if the conditional expression gives true value and previous conditions are false. It is possible to add any number of Else If blocks based on the requirements.

Look at this example where we have three choices based on the age group.

package main

import "fmt"

func main() {
        age := 10
        if age < 10 {
        fmt.Println("Junior", age)
    } else if age < 20 {
        fmt.Println("Senior", age)
    } else {
        fmt.Println("Other", age)
    }
}

In the above example, the value printed will be either Junior, Senior or Other. You can change age value and run the program again and again to see the outputs. The Else If can be repeated here to create more choices.

3.1.5. Inline Statement

In the previous section, the variable age was only within the If, Else If and Else blocks. And that variable was not used used afterwards in the function. Go provides a syntax to define a variable along with the If where the scope of that variable will be within the blocks. In fact, the syntax can be used for any valid Go statement. However, it is mostly used for declaring variables.

Here is an example where a variable named money is declared along with the If control structure.

package main

import "fmt"

func main() {
    if money := 20000; money > 15000 {
        fmt.Println("I am going to buy a car.", money)
    } else {
        fmt.Println("I am going to buy a bike.", money)
    }
    // can't use the variable `money` here
}

As mentioned above, the variable declared by the inline statement is available only within the scope of If, Else If and Else blocks. So, the variable money cannot be used outside the blocks.

It is possible to make any valid Go statement as part of the If control structure. For example, it is possible to call a function like this:

if money := someFunction(); money > 15000 {

3.2. For

3.2.1. Basic For

As we have seen briefly in the Quick Start, the For control structure helps to create loops to repeat certain actions. The For control structure has few syntax variants.

Consider a program to print few names.

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
    for i := 0; i < len(names); i++  {
        fmt.Println(names[i])
    }
}

You can save the above program in a file named names.go and run it like this:

$ go run name.go
Tom
Polly
Huck
Becky

In the above example, names variable hold a slice of strings. The value of i is initialized to zero and incremented one by one. The i++ statement increment the value of i. The second part of for loop check if value of i is less than length of the slice. The built-in len gives the length of slice.

Other programming languages offer many ways for iterations. Some of the examples are while and do…while. But in Go using syntactic variation of for loop meets all requirements. Functional languages prefer to use recursion instead of iteration.

3.2.2. Break Loop Prematurely

Sometimes the iteration should be stopped prematurely on certain condition. This can be achieved using the If control structure and break statement. We have already studied If control structure from the previous major section. The break keyword allows to create a break statement. The break statement end the loop immediately. Though any other code followed by For loop will be executed.

Let’s alter the previous program to stop printing after the name Polly found.

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
    for i := 0; i < len(names); i++  {
        fmt.Println(names[i])
        if names[i] == "Polly" {
            break
        }
    }
}

In the above example, we added an If control structure to check for the value of name during each iteration. If the value matches Polly, break statement will be executed. The break statement makes the For loop to end immediately.

As you can see in the above code, the break statement can stand alone without any other input. There is alternate syntax with label similar to how goto works, which we are going to see below. This is useful when you have multiple loops and want to break a particular one, may be the outer loop.

To understand this better, let’s consider an example. The problem is to to change print the name given the slice until a word with u found.

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
Outer:
    for i := 0; i < len(names); i++ {
        for j := 0; j < len(names[i]); j++ {
            if names[i][j] == 'u' {
                break Outer
            }
        }
        fmt.Println(names[i])
    }
}

In the above example, we are declaring a label statement just before the first For loop. There is an inner loop to iterate through the name string and check for the presence of character u. If the character u is found, then it will break the outer loop. If the label Outer is not used in the break statement, then the inner loop will be stopped.

3.2.3. Partially Execute Loop Statements

Sometimes statements within For loop should be executed on certain iterations. Go has a continue statement to proceed loop without executing further statements.

Let’s modify the previous problem to print all names except Polly.

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
    for i := 0; i < len(names); i++ {
        if names[i] == "Polly" {
            continue
        }
        fmt.Println(names[i])
    }
}

In the above code, the continue statement makes it proceed with next iteration in the loop without printing Polly.

Similar to break statement with label, continue also can be used with a label. This is useful if there are multiple loops and want to continue a particular loop, say the outer one.

Let’s consider an example where you need to print names which doesn’t have character u in it.

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
Outer:
    for i := 0; i < len(names); i++ {
        for j := 0; j < len(names[i]); j++ {
            if names[i][j] == 'u' {
                continue Outer
            }
        }
        fmt.Println(names[i])
    }
}

In the above code, just before the first loop a label is declared. Later inside the inner loop to iterate through the name string and check for the presence of character u. If the character u is found, then it will continue the outer loop. If the label Outer is not used in the continue statement, then the inner loop will be proceed to execute.

3.2.4. For with Outside Initialization

The statement for value initialization and the last pat to increment value can be removed from the For control structure. The value initialization can be moved outside For and value increment can be moved inside loop.

The previous example can be changed like this:

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
    i := 0
    for i < len(names) {
        i++
        fmt.Println(names[i])
    }
}

In the above example, the scope of variable i is outside For loop code block. Whereas in the previous section, when the variable declared along with For loop, the scope of that variable was within the loop code block.

3.2.5. Infinite Loop

For loop has yet another syntax variant to support infinite loop. You can create a loop that never ends until explicitly stopped using break or exiting the whole program. To create an infinite loop, you can use the for keyword followed by the curly bracket.

If any variable initialization is required, that should be declared outside the loop. Conditions can be added inside the loop.

The previous example can be changed like this:

package main

import "fmt"

func main() {
    names := []string{"Tom", "Polly", "Huck", "Becky"}
    i := 0
    for {
        if i >= len(names) {
            break
        }
        fmt.Println(names[i])
        i++
    }
}

3.2.6. Range Loops

The range clause form of the for loop iterates over a slice or map. When looping over a slice using range, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.

The previous example for loop can be simplified using the range clause like this:

package main

import "fmt"

func main() {
    characters := []string{"Tom", "Polly", "Huck", "Becky"}
    for _, j := range characters {
        fmt.Println(j)
    }
}

The underscore is called blank indentifier, the value assigned to that variable will be ignored. In the above example, the index values will be assigned to the underscore.

The range loop can be used with map. Here is an example:

package main

import "fmt"

func main() {
    var characters = map[string]int{
                "Tom": 8,
                "Polly": 51,
                "Huck": 9,
                "Becky": 8,
    }
    for name, age := range characters {
        fmt.Println(name, age)
    }
}

3.3. Switch Cases

3.3.1. Basic Switch

In addition to the if condition, Go provides switch case control structure for branch instructions. The switch case is more convenient if many cases need to be handled in the branch instructions.

The below program use a switch case to print number names based on the value.

package main

import "fmt"

func main() {
    v := 1
    switch v {
    case 0:
            fmt.Println("zero")
    case 1:
            fmt.Println("one")
    case 2:
            fmt.Println("two")
    default:
            fmt.Println("unknown")
    }
}

In this case, the value of v is 1, so the case that is going to execute is 2nd one. This will be the output.

$ go run switchbasic.go
one

If you change the value of v to 0, it’s going to print zero and for 2 it will print two. If the value is any number other than 0, 1 or 2, it’s going to print unknown.

3.3.2. Fallthrough

The cases are evaluated top to bottom until a match is found. If a case is matched, the statements within that case will be executed. And no other case will be executed unless a fallthrough statement is used. The fallthrough must be the last statement within the case.

Here is a modified version with fallthrough

package main

import "fmt"

func main() {
    v := 1
    switch v {
    case 0:
            fmt.Println("zero")
    case 1:
            fmt.Println("one")
            fallthrough
    case 2:
            fmt.Println("two")
    default:
            fmt.Println("unknown")
    }
}

If you run this program, it will print one followed by two.

$ go run switchbasic.go
one
two

3.3.3. Break

As you can see from the above examples, the switch statements break implicitly at the end of each cases. The fallthrough statement can be used to passdown control to the next case. However, sometimes execution should be stopped early without executing all statements. This can can be achieved using break statements.

Here is an example:

package main

import (
    "fmt"
    "time"
)

func main() {
    v := "Becky"
    t := time.Now()
    switch v {
    case "Huck":
        if t.Hour() < 12 {
            fmt.Println("Good morning,", v)
            break
        }
        fmt.Println("Hello,", v)
    case "Becky":
        if t.Hour() < 12 {
            fmt.Println("Good morning,", v)
            break
        }
        fmt.Println("Hello,", v)
    default:
        fmt.Println("Hello")
    }
}

In the above example, morning time greeting is different.

3.3.4. Multiple Cases

Multple cases can be presented in comma-separated lists.

Here is an example.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    o := "=="
 7    switch o {
 8    case "+", "-", "*", "/", "%", "&", "|", "^", "&^", "<<", ">>":
 9        fmt.Println("Arithmetic operator")
10    case "==", "!=", "<", "<=", ">", ">=":
11        fmt.Println("Comparison operators")
12    case "&&", "||", "!":
13        fmt.Println("Logical operators")
14    default:
15        fmt.Println("Unknown operator")
16    }
17}

In this example, if any of the value is matched in the given list, that case will be executed.

3.3.5. Without Expression

If the switch has no expression it switches on true. This is useful to write an if-else-if-else chain.

Let’s take the example program used earlier when Else If was introduced:

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    age := 10
 7    switch {
 8    case age < 10:
 9        fmt.Println("Junior", age)
10    case age < 20:
11        fmt.Println("Senior", age)
12    default:
13        fmt.Println("Other", age)
14    }
15}

3.4. Defer Statements

Sometimes it will require to force certain things to do before a function returns. For example, closing an opened file descriptor. Go provides the defer statements to do these kind of cleanup actions.

A defer statement add a function call into a stack. The stack of function call executes at the end of the surrounding function in a last-in-first-out (LIFO) order. Defer is commonly used to perform various clean-up actions.

Here is a simple example:

package main

import (
    "fmt"
    "time"
)

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}

The above program is going to print hello followed by world.

If there are multiple defer statements, it will execute in last-in-first-out (LIFO) order.

Here is a simple example to demonstrate it:

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
}

The above program will print this output:

4
3
2
1
0

The arguments passed the the deferred call are evaluated immediately. But the deferred call itself is not executed until the function returns. Here is a simple example to demonstrate it:

package main

import (
    "fmt"
    "time"
)

func main() {
    defer func(t time.Time) {
        fmt.Println(t, time.Now())
    }(time.Now())
}

When you run the above program, you can see a small difference in time. The defer can also be used to recover from panic, which will be discussed in the next section.

3.5. Deffered Panic Recover

We have discussed the commonly used control structures including if, for, and switch. This section is going to discuss a less commonly used set of control structures: defer, panic, and recover. We have discussed the use of the defer statement in the previous section. In this section, you are going to learn how to use the defer along with panic and recover.

Few important points about defer, panic, and recover:

  • A panic causes the program stack to begin unwinding and recover can stop it

  • Deferred functions are still executed as the stack unwinds

  • If recover is called inside such a deferred function, the stack stops unwinding

  • The recover returns the value (as an interface{}) that was passed to panic

  • A panic cannot be recovered by a different goroutine

Here is an example:

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    defer func() {
 7        if r := recover(); r != nil {
 8            fmt.Println("Recoverd", r)
 9        }
10    }()
11    panic("panic")
12}

3.6. Goto

The goto statement can be used to jump control to another statement. The location to where the control should be passed is specified using label statements. The goto statement and the corresponding label should be within the same function. The goto cannot jump to a label inside another code block.

package main

import "fmt"

func main() {
    num := 10
    goto Marker
    num = 20
Marker:
    fmt.Println("Value of num:", num)
}

You can save the above program in a file named goto1.go and run it like this:

$ go run goto1.go
Value of num: 10

In the above code Marker: is a label statement. A label statement is a valid identifier followed by a colon. A label statement will be target for goto, break or continue statement. We will look at break and continue statement when we study the For control structure.

The goto statement is writen using the goto keyword followed by a valid label name. In the above code, immediately after the goto statement, there is a statement to assign a different value to num. But that statement is never getting executed as the goto makes the program to jump to the label.

3.7. Exercises

Exercise 1: Print whether the number given as the command line argument is even or odd.

Solution:

You can store the program with a file named evenodd.go. Later you can compile this program and then you will get a binary executable with name as evenodd. You can execute this program like this:

./evenodd 3
3 is odd
./evenodd 4
4 is even

In the above program, the 3 and 4 are the command line arguments. The command line arguments can be accessed from Go using the slice available under os package. The arguments will be available with exported name as Args and individual items can be accessed using the index. The 0th index contains the program itself, so it can be ignored. To access the 1st command argument use os.Args[1]. The values will be of type string which can be converted later.

 1package main
 2
 3import (
 4    "fmt"
 5    "os"
 6    "strconv"
 7)
 8
 9func main() {
10    i := os.Args[1]
11    n, err := strconv.Atoi(i)
12    if err != nil {
13        fmt.Println("Not a number:", i)
14        os.Exit(1)
15    }
16    if n%2 == 0 {
17        fmt.Printf("%d is even\n", n)
18    } else {
19        fmt.Printf("%d is odd\n", n)
20    }
21}

Exercise 2: Write a program to print numbers below 20 which are multiples of 3 or 5.

Solution:

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6     for i := 1; i < 20; i++ {
 7             if i%3 == 0 || i%5 == 0 {
 8                     fmt.Println(i)
 9             }
10     }
11}

3.7.1. Additional Exercises

Answers to these additional exercises are given in the Appendix A.

Problem 1: Write a program to print greetings based on time. Possible greetings are Good morning, Good afternoon and Good evening.

Problem 2: Write a program to check if the given number is divisible by 2, 3, or 5.

3.8. Summary

This chapter explained the control structures available in Go, except those related to concurrency. The if control structure was covered first, then the for loop was explained. The switch cases were discussed later. Then the defer statement and finally, the goto control structure was explained in detail. This chapter also briefly explained accessing command line arguments from the program.

Control structures are used to control the flow of execution in a program. They allow you to execute code conditionally, repeatedly, or in a specific order.

  • The if control structure is used to execute code if a condition is met.

  • The for loop is used to execute code repeatedly until a condition is met.

  • The switch statement is used to execute code based on the value of a variable.

  • The defer statement is used to execute code after the current function has finished executing.

  • The goto statement is used to jump to a specific label in the code.