Breaking in Go

One of Go’s advantages is its familiar syntax and vocabulary. Parentheses, brackets, and braces have the meaning you expect. Programs are composed of well known keywords like var, const, for, or return. This familiarity, however, means that one might forget that Go might have slightly different semantic for those terms.

One such example is the keyword break. Recently, we wrote a program which boiled down to an equivalent of:

for i := 0; i < 10; i++ {
    fmt.Println("Line:", i)
    switch i {
    case 5:
        break
    }
}

The desired output was to terminate the loop when a condition was reached inside the switch statement. This code instead prints all ten lines. As the Go language specification states break terminates innermost for, switch, or select.

OK, that’s not the desired behaviour, let’s fix it and tell Go to terminate the loop. An approach natural to anyone who has programmed in PHP is to add the number of levels the break should terminate:

for i := 0; i < 10; i++ {
    fmt.Println("Line:", i)
    switch i {
    case 5:
        break 2
    }
}

This doesn’t compile! I didn’t check if this is a speciality of PHP or if there are other languages which understand this expression. In any case, it’s point illustrating that apparent similarities might hide semantic differences.

The correct Go approach is to specify a label and then say that the execution should continue at that level:

L:
for i := 0; i < 10; i++ {
    fmt.Println("Line:", i)
    switch i {
    case 5:
        break L
    }
}

In essence, it is a special case of goto statement which has had bad reputation ever since structured programming became a thing. It’s also interesting to note that there are languages which don’t have a goto and the same problem would have be to solved by introducing a boolean variable:

cont := true
for i := 0; cont && i < 10; i++ {
    fmt.Println("Line:", i)
    switch i {
    case 5:
        cont = false
        break
    }
}

or wrapping the whole loop in a function:

func () {
    for i := 0; i < 10; i++ {
        fmt.Println("Line:", i)
        switch i {
        case 5:
            return
        }
    }
}()

Looking at the alternatives, a masked goto is a reasonable solution. Though, I wouldn’t be surprised if people kept avoiding it purely because “goto is evil”.

As a side note, it’s worth mentioning that the code at the beginning came to be because the actual condition was originally a much more complex if which was refactored into a more readable switch. However, this refactoring meant that break was pushed one level too deep.

We're looking for developers to help us save energy

If you're interested in what we do and you would like to help us save energy, drop us a line at jobs@enectiva.cz.

comments powered by Disqus