How To Write Switch Statements in Go
How To Write Switch Statements in Go

How To Write Switch Statements in Go

PostedOctober 23, 2019 3.8k views Go Development

Introduction

Conditional statements give programmers the ability to direct their programs to take some action if a condition is true and another action if the condition is false. Frequently, we want to compare some variable against multiple possible values, taking different actions in each circumstance. It’s possible to make this work using if statements alone. Writing software, however, is not only about making things work but also communicating your intention to your future self and other developers. switch is an alternative conditional statement useful for communicating actions taken by your Go programs when presented with different options.

Everything we can write with the switch statement can also be written with if statements. In this tutorial, we’ll look at a few examples of what the switch statement can do, the if statements it replaces, and where it’s most appropriately applied.

Structure of Switch Statements

Switch is commonly used to describe the actions taken by a program when a variable is assigned specific values. The following example demonstrates how we would accomplish this using if statements:

package main

import "fmt"

func main() {
    flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}

    for _, flav := range flavors {
        if flav == "strawberry" {
            fmt.Println(flav, "is my favorite!")
            continue
        }

        if flav == "vanilla" {
            fmt.Println(flav, "is great!")
            continue
        }

        if flav == "chocolate" {
            fmt.Println(flav, "is great!")
            continue
        }

        fmt.Println("I've never tried", flav, "before")
    }
}

This will generate the following output:

Output
chocolate is great! vanilla is great! strawberry is my favorite! I've never tried banana before

Within main, we define a slice of ice-cream flavors. We then use a for loop to iterate through them. We use three if statements to print out different messages indicating preferences for different ice-cream flavors. Each if statement must use the continue statement to stop execution of the for loop so that the default message at the end is not printed for the preferred ice-cream flavors.

As we add new ice-cream preferences, we have to keep adding if statements to handle the new cases. Duplicated messages, as in the case of "vanilla" and "chocolate", must have duplicated if statements. To future readers of our code (ourselves included), the repetitive nature of the if statements obscures the important part of what they are doing—comparing the variable against multiple values and taking different actions. Also, our fallback message is set apart from the conditionals, making it appear unrelated. The switch statement can help us organize this logic better.

The switch statement begins with the switch keyword and is followed, in its most basic form, with some variable to perform comparisons against. This is followed by a pair of curly braces ({}) where multiple case clauses can appear. Case clauses describe the actions your Go program should take when the variable provided to the switch statement equals the value referenced by the case clause. The following example converts the previous example to use a switch instead of multiple if statements:

package main

import "fmt"

func main() {
    flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}

    for _, flav := range flavors {
        switch flav {
        case "strawberry":
            fmt.Println(flav, "is my favorite!")
        case "vanilla", "chocolate":
            fmt.Println(flav, "is great!")
        default:
            fmt.Println("I've never tried", flav, "before")
        }
    }
}

The output is the same as before:

Output
chocolate is great! vanilla is great! strawberry is my favorite! I've never tried banana before

We’ve once again defined a slice of ice-cream flavors in main and used the range statement to iterate over each flavor. This time, however, we’ve used a switch statement that will examine the flav variable. We use two case clauses to indicate preferences. We no longer need continue statements as only one case clause will be executed by the switch statement. We’re also able to combine the duplicated logic of the "chocolate" and "vanilla" conditionals by separating each with a comma in the declaration of the case clause. The default clause serves as our catch-all clause. It will run for any flavors that we haven’t accounted for in the body of the switch statement. In this case, "banana" will cause default to execute, printing the message I've never tried banana before.

This simplified form of switch statements addresses the most common use for them: comparing a variable against multiple alternatives. It also provides conveniences for us where we want to take the same action for multiple different values and some other action when none of the listed conditions are met by using the provided default keyword.

When this simplified form of switch proves too limiting, we can use a more general form of switch statement.

General Switch Statements

switch statements are useful for grouping collections of more complicated conditionals to show that they are somehow related. This is most commonly used when comparing some variable against a range of values, rather than specific values as in the earlier example. The following example implements a guessing game using if statements that could benefit from a switch statement:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    target := rand.Intn(100)

    for {
        var guess int
        fmt.Print("Enter a guess: ")
        _, err := fmt.Scanf("%d", &guess)
        if err != nil {
            fmt.Println("Invalid guess: err:", err)
            continue
        }

        if guess > target {
            fmt.Println("Too high!")
            continue
        }

        if guess < target {
            fmt.Println("Too low!")
            continue
        }

        fmt.Println("You win!")
        break
    }
}

The output will vary depending on the random number selected and how well you play the game. Here is the output from one example session:

Output
Enter a guess: 10 Too low! Enter a guess: 15 Too low! Enter a guess: 18 Too high! Enter a guess: 17 You win!

Our guessing game needs a random number to compare guesses against, so we use the rand.Intn function from the math/rand package. To make sure we get different values for target each time we play the game, we use rand.Seed to randomize the random number generator based on the current time. The argument 100 to rand.Intn will give us a number in the range 0–100. We then use a for loop to begin collecting guesses from the player.

The fmt.Scanf function gives us a means to read user input into a variable of our choosing. It takes a format string verb that converts the user’s input into the type we expect. %d here means we expect an int, and we pass the address of the guess variable so that fmt.Scanf is able to set that variable. After handling any parsing errors we then use two if statements to compare the user’s guess to the target value. The string that they return, along with bool, controls the message displayed to the player and whether the game will exit.

These if statements obscure the fact that the range of values that the variable is being compared against are all related in some way. It can also be difficult, at a glance, to tell if we missed some part of the range. The next example refactors the previous example to use a switch statement instead:

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    target := rand.Intn(100)

    for {
        var guess int
        fmt.Print("Enter a guess: ")
        _, err := fmt.Scanf("%d", &guess)
        if err != nil {
            fmt.Println("Invalid guess: err:", err)
            continue
        }

        switch {
        case guess > target:
            fmt.Println("Too high!")
        case guess < target:
            fmt.Println("Too low!")
        default:
            fmt.Println("You win!")
            return
        }
    }
}

This will generate output similar to the following:

Output
Enter a guess: 25 Too low! Enter a guess: 28 Too high! Enter a guess: 27 You win!

In this version of the guessing game, we’ve replaced the block of if statements with a switch statement. We omit the expression argument to switch because we are only interested in using switch to collect conditionals together. Each case clause contains a different expression comparing guess against target. Similar to the first time we replaced if statements with switch, we no longer need continue statements since only one case clause will be executed. Finally, the default clause handles the case where guess == target since we have covered all other possible values with the other two case clauses.

In the examples that we’ve seen so far, exactly one case statement will be executed. Occasionally, you may wish to combine the behaviors of multiple case clauses. switch statements provide another keyword for achieving this behavior.

Fallthrough

Sometimes you will want to reuse the code that another case clause contains. In these cases, it’s possible to ask Go to run the body of the next case clause listed using the fallthrough keyword. This next example modifies our earlier ice cream flavor example to more accurately reflect our enthusiasm for strawberry ice cream:

package main

import "fmt"

func main() {
    flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}

    for _, flav := range flavors {
        switch flav {
        case "strawberry":
            fmt.Println(flav, "is my favorite!")
            fallthrough
        case "vanilla", "chocolate":
            fmt.Println(flav, "is great!")
        default:
            fmt.Println("I've never tried", flav, "before")
        }
    }
}

We will see this output:

Output
chocolate is great! vanilla is great! strawberry is my favorite! strawberry is great! I've never tried banana before

As we’ve seen previously, we define a slice of string to represent flavors and iterate through this using a for loop. The switch statement here is identical to the one we’ve seen before, but with the addition of the fallthrough keyword at the end of the case clause for "strawberry". This will cause Go to run the body of case "strawberry":, first printing out the string strawberry is my favorite!. When it encounters fallthrough it will run the body of the next case clause. This will cause the body of case "vanilla", "chocolate": to run, printing strawberry is great!.

The fallthrough keyword is not used often by Go developers. Usually, the code reuse realized by using fallthrough can be better obtained by defining a function with the common code. For these reasons, using fallthrough is generally discouraged.

Conclusion

switch statements help us convey to other developers reading our code that a set of comparisons are somehow related to each other. They make it much easier to add different behavior when a new case is added in the future and make it possible to ensure that anything we forgot is handled properly as well with default clauses. The next time you find yourself writing multiple if statements that all involve the same variable, try rewriting it with a switch statement—you’ll find it easier to rework when it comes time to consider some other alternative value.

If you’d like to learn more about the Go programming language, check out the entire How To Code in Go series.

0 Comments

Creative Commons License