Goto Hell With Labels in Golang
Exploring goto statement in Go and learning how to use them and how the standard library handles flow control with goto and labels
by Percy Bolmér, August 22, 2021
Many programming languages have some sort of goto statement. If you’re not familiar with goto it is a way of jumping in code into a certain position. It can be used to control the flow of execution. This kind of statement is usually very hated in developer communities because it can lead to hard-to-follow code. I have always liked the goto statement, and I started to wonder, is it used in the standard library of Go and decided to take a deep dive.
I’ve met many Go developers who do not know that goto exists. However it does, and it is used quite a bit in the standard library. A simple search in the standard library reveals that the goto statement is used 493 times in 98 different files if we skip comments and non-Go files. We will look at some of these later in the article, let’s first learn how the statement works.
Goto statement in Go
The way it works is that you can write down a label which is a custom keyword that can be recognized by the goto statement. A label is a string and is created by setting a semicolon after the label.
label:
// Code to execute in label
Go has some safety in its implementation in that a label has to be created in the same function that the goto statement. A label has to be used, the compiler will catch this for you. The same goes if you create a variable after a label, all variables used in the label have to be defined earlier in the scope.
The best way to understand it is probably to see it, here is a super simple example that shows how to set a label and then jump to it, skipping any code in between. We will create a for loop that will execute 10 times, but when i is equal to 5 we will use a goto to break the execution. Note that the label has to be created inside the same function, or the compiler will fail. The same goes if the label is not used, then the compiler will fail again. In the example, I have named the label into the exit.
That’s it! There is no other magic, it is that simple. Remember that you cannot jump to labels outside of the same function. This is a nice safeguard that removes a lot of the confusion.
A thing to look out for is that labels will be executed if your regular code flow traverses into them without returning. This can happen if you have multiple labels at the end of a function.
I’ll add an if statement to showcase this, if the I counter becomes 6 it will print. I will never become 6 because when it is 5 we will break out of the for loop, so we will never trigger the second label, right? The first time I used labels that were how I thought it would work, but it will run anything below the first label, like normal code execution flow.
Goto in standard library
Let’s explore how the goto statement is used inside the standard library so that we can get a better grip of good usage. I’m going to assume that the std lib is considered good usage.
One great example I’ve found is the go/scanner which is a package for scanning Go source code. Scanning text files can be a really hard task, especially if you don’t know what is inside them. It is common to use a technique called Lexical analysis. I won’t describe the exact internal workings, but you create strings called tokens that can be used to identify the current location of the text. Imagine yourself writing a text scanner that reads source code and validates it yourself, It is no trivial task.
The section we are going to look at which is fairly basic is the one where the Go team scans for comments in the source code. The function is scanComment and contains a label named exit, just like the example we used before.
To make the code a bit more understandable I’d like to give some context. s.ch is the current character being parsed. s.next() reads the next Unicode character into s.ch There is a goto at the first if when it’s a regular comment and another when it is a multi-line comment. This is a great example of how to skip code blocks without using multiple flags to control the state. They could have done this without a goto using a few booleans, such as an isSingleLineComment and if that is true, then skip going into the multiline parsing. But they avoid a lot of complicated flow logic but using a simple goto and I like that solution.
The second goto is used when they are parsing a multiline comment. In it, they keep iterating until they find a comment closure marked by */. It is a loop that runs until the end of the file if no closing appears. Instead of using a break to cancel the for loop when the condition is met, they use a goto which is another great way to control the flow. They also bypass the s.error which is used to set errors found.
The second example of usage is found in go/types in the exprInternal function which is used in the core Go to type check. This is a great example of when we have a switch that needs to check many conditions and grows a bit too big. They use continue and break as usual to manage the flow, but they also use goto to standardize the error handling and avoid duplicating code.
When to Use goto in Go
Using the experience we got from the standard library we can see that usage sometimes makes sense. And considering that Go only allows labels within the same function I think it does not make code hard to follow.
The occasions where I would use it is when the regular flow control mechanisms do not cut it without becoming complex, here are a few bullet points of cases.
Avoiding conditional flags for storing states and jumping over code blocks like in the go/scanner
Standardize an exit plan, in cases where you have a switch or many for loops that all can cause the same standard exit like in go/types/expr
Complex logic that would require nested for loops can be made a lot easier the read and understand by avoiding nested loops by using goto
Avoid duplicate code by handling common pattern in a label as in the Error label in go/types/expr
What is your opinion about the goto statement? Do you think it increases complexity and confusion, or do you think it is a good solution to handle the flow?
This is it for this article, thank you for reading. And as always I appreciate feedback, or just reach out if you have questions. You find me here on any of my social medias linked below.
If you like my writing feel free to follow me for more. I recently wrote about structured logs in Go. How To Use Structured JSON Logging in Golang Applications
If you enjoyed my writing, please support future articles by buying me an Coffee