6 minutes read

You know that code runs line by line. Of course, you can utilize certain statements to skip lines (if statement, for example), or use the code from another section (func). Unfortunately, all these tools work under some conditions, and they can call only specific sections of code. Golang, however, has operators that give you the ability for unconditional transition. Yeah, you can just jump between the lines of code!

In this topic, you will get acquainted with two useful operators. The first of them is a label. Just like a label in a store or a "Hello, my name is..." sticker. A label marks the code, and you can easily get back to it later. The second one is an unconditional transition operator. It can help you jump to a label. Let's check out how they work!

Label syntax

Hello, my name is code! What do you do when you need to mark some lines of code? In most cases, you use comments for that. It works, but the text in comments has no effect on the code execution. What if you want to get back to a line of code using programming language statements? You could use a function, but it runs a section of code and returns a strictly defined value. In Golang, there is an operator that you can use to mark a line of your code. It's called a label. Like other operators, it has a few rules of use:

  • Labels can't include any whitespaces;

  • Labels must start with a letter;

  • After the label name, you must put the colon symbol;

  • Labels mark lines of code, not sections.

We will take a closer look at the last rule in the goto section. For now, let's consider the most common use of labels.

The first case is an interaction with a loop. More specifically, interaction with the break statement of a loop. You can mark the beginning of a loop and then break it using the label.

package main

import "fmt"

func main() {
    var num, divCount, targetCount int
    fmt.Scan(&targetCount)

NumberLoop: //label
    for {
        divCount = 0
        for i := 2; i < num; i++ {
            if num%i == 0 {
                divCount++
            }
            if divCount >= targetCount {
                break NumberLoop // break by the label
            }
        }

        num++
    }

    fmt.Println(num)
}

// Input:
// 123
// Output:
// 83160

There are two infinite loops in the code above: the outer loop increments the number, while the inner loop counts dividers of the number. Imagine that you need to find out the first number that has equal or more count of dividers (not including the first and the final number) than the targetCount. When we find the required count inside the inner loop, the search process must stop (break both loops). To make it possible, we use the break statement with a label.

Let's see what happens if we use the break statement without a label.

package main

import "fmt"

func main() {
    var num, divCount, targetCount int
    fmt.Scan(&targetCount)

    // NumberLoop: the label is not used, and it returns an error
    for {
        divCount = 0
        for i := 2; i < num; i++ {
            if num%i == 0 {
                divCount++
            }
            if divCount >= targetCount {
                break // break without the label
            }
        }

        num++
    }

    fmt.Println(num)
}

// Output:
// ^Csignal: interrupt

As you can see, we end up getting an infinite loop. The break statement works when the divCount is equal or more than the targetCount, and it goes out of the inner loop. However, the outer loop continues execution. And all that's left to do is to stop it manually.

Goto syntax

The goto operator works with labels. The syntax is pretty simple: you should write goto, followed by the name of the label. It affects the order of code execution: the program will execute the lines of code placed after the given label right after the goto statement.

package main

import "fmt"

func main() {
    fmt.Println("I'm printed")

    goto EndOfTheCode

    fmt.Println("I'm not printed")

EndOfTheCode:
    fmt.Println("Print at the end")
}

// Output:
// I'm printed
// Print at the end

You need to remember that labels mark the lines of code, not its sections (enclosed in {}). It means that absolutely all lines after the label will be run again. Thus, if we place a label before goto, it may cause an infinite loop:

package main

import "fmt"

func main() {
    i := 0

Start_of_the_loop: // label
    fmt.Println(i)
    i += 1

    goto Start_of_the_loop // goto statement
}

// Output
// 0
// 1
// ...
// 256406
// 256407
// ^Csignal: interrupt

In this case, the end of the program is ignored, and you need to end the runtime yourself. This happens because you get a loop without the for statement! You need to provide the exit condition to end the loop (use another goto or use the if statement).

Best practices

In development, using goto statements is considered a bad practice. As you may see, it can break the common way of code execution. However, in some cases, it can give a positive effect. For example, you can use it to separate the sections of code inside one scope. Besides, it can give a significant increase in speed when executing complex code (though it has almost no effect on simple scripts).

One of the most often use cases of goto is returning to the parent scope. For example, you can omit the else statement:

package main

import "fmt"

func main() {
    var num int
    fmt.Scan(&num)

    if num%2 == 0 {
        fmt.Println("is even")
        goto TheEnd
    }
    // else
    fmt.Println("is odd")

TheEnd:
    fmt.Println("end")
}

// Input:
// 2
// Output:
// is even
// end

// Input:
// 3
// Output:
// is odd
// end

Such syntax is suitable if you have a complex code inside the if-else sections.

However, if you want to use a goto statement, don't abuse it! The more you use the goto statement in your code, the closer you are to turning your code into an unreadable dump of words. This pattern is known as the "Spaghetti code".

To prevent that, before using the goto statement, think about the reasons for doing so. If you decide to use it, try to keep it to a minimum. In addition, always use goto inside one scope of code (a section inside the curly braces). To tell the truth, the Golang compiler doesn't let you run the code with transitions between scopes. Let's look at a bad example of using goto:

package main

import "fmt"

func main() {
    var num int
    fmt.Scan(&num)

    isEven := num%2 == 0
    isPositive := num > 0

    if isEven {
        goto evenAction // trying to jump to the next if-statement
    }

    if isPositive {
        fmt.Println("is positive!")
    evenAction: // target Label
        fmt.Println("is even!")
    }
}

// Output:
// ./script.go:13:8: goto evenAction jumps into block starting at ./script.go:16:16

Here, the goto statement tries to jump into another if-section. Golang stops the execution and raises the exception. Let's rewrite the same code without using goto:

package main

import "fmt"

func main() {
    var num int
    fmt.Scan(&num)

    isEven := num%2 == 0
    isPositive := num > 0

    if isPositive {
        fmt.Println("is positive!")
    }

    if isEven {
        fmt.Println("is even!")
    }
}

// Input:
// 4
// Output:
// is positive!
// is even!

Conclusion

In this topic, you've learned about labels and the goto operator. A label is just a mark we put on one line of code, whereas the goto statement can have a really strong effect on your code. You need to use it carefully, since it can break the usual order of code execution. Utilizing such statements too much can turn your code into an incomprehensible set of words. It doesn't mean that using the goto statement is a bad pattern. It's only bad if you abuse it.

And now let's recall the main points of this topic:

  • Labels have strict syntax rules;

  • Labels are different from functions;

  • You can use labels to break an outer loop;

  • You can use the goto operator only with labels;

  • The code between goto and a label will be ignored.

12 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo