Labels in Go

Michał Łowicki
golangspec
4 min readAug 15, 2016

--

Label is used in break and continue statement where it’s optional but it’s required in goto statement. Scope of the label is the function where it’s declared. It doesn’t start after the declaration though as with (short) variable declaration but is available for the whole function body:

func main() {
fmt.Println(1)
goto End
fmt.Println(2)
End:
fmt.Println(3)
}

(notice that label is declared after it’s used in goto statement)

> ./bin/sandbox
1
3

Scope doesn’t contain nested functions so it’s illegal to write:

    func() {
fmt.Println(“Nested function”)
goto End
}()
End:

as it gives “label End not defined” error. This code unveils another compilation-time issue “label End defined and not used” which is a consequence of the requirement that every declared label has to be used.

Labels are not block scoped (more about blocks and scoping in “Blocks in Go” and “Scopes in Go”) so it’s impossible to redeclare label inside nested block:

    goto X
X:
{
X:
}

as Go compiler will complain about already declared label.

Identifiers of the labels live in a separate space so they don’t conflict with i.e. variables identifiers. The code below works just fine even that x is used for two things:

    x := 1
goto x
x:
fmt.Println(x)

break statement

break statement is traditionally used to terminate innermost for or switch statement. In Go it’s also possible to end this way novel select statement.

People having experience with languages like C and derivates know that each case clause needs to have break as a last statement. Otherwise execution in transferred to the next case clause which is not what programmer wants most of the time. Go however works conversely — there is a fallthrough statement to explicitly move to the next clause:

switch 1 {
case 1:
fmt.Println(1)
case 2:
fmt.Println(2)
}
> ./bin/sandbox
1
switch 1 {
case 1:
fmt.Println(1)
fallthrough
case 2:
fmt.Println(2)
}
> ./bin/sandbox
1
2

fallthrough can be used only as a last statement inside case clause.

break statement cannot span over function boundaries:

func f() {
break
}
func main() {
for i := 0; i < 10; i++ {
f()
}
}

as it gives compilation error “break is not in a loop”.

Normally break without optional label terminates innermost for, switch or select statement. Adding label gives more possibilities…

Label for break statement must be the one associated with enclosing for, switch or select statement. It’s impossible to compile:

FirstLoop:
for i := 0; i < 10; i++ {
}
for i := 0; i < 10; i++ {
break FirstLoop
}

as it throws “invalid break label FirstLoop” error message since label is associated with not enclosing for loop.

It’s valid to terminate other outer (enclosing) loops:

OuterLoop:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
fmt.Printf(“i=%v, j=%v\n”, i, j)
break OuterLoop
}
}
> ./bin/sandbox
i=0, j=0

If break statement is placed inside f.ex. loop it doesn’t mean it can only terminate loop:

SwitchStatement:
switch 1 {
case 1:
fmt.Println(1)
for i := 0; i < 10; i++ {
break SwitchStatement
}
fmt.Println(2)
}
fmt.Println(3)
> ./bin/sandbox
1
3

It’s possible to terminate for, switch or select no matter where break statement is located.

continue statement

It works in a similar way to break statement but begins next iteration instead of stopping and can be used only for loops:

OuterLoop:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf(“i=%v, j=%v\n”, i, j)
continue OuterLoop
}
}
> ./bin/sandbox
i=0, j=0
i=1, j=0
i=2, j=0

goto statement

It’s used to move execution to the labeled statement:

    i := 0
Start:
fmt.Println(i)
if i > 2 {
goto End
} else {
i += 1
goto Start
}
End:

(notice that “End” labels empty statement which is also permitted)

In contrast to break or continue statements, label is required.

goto can move control only within the same function. Since it’s possible to transfer execution forward there are additional two rules:

  • any variable declaration cannot be skipped so causing a variable to come into scope if it wasn’t at the point where goto is used:
    goto Done
v := 0
Done:
fmt.Println(v)

Compiler will detect it as an error “goto Done jumps over declaration of v at…”.

  • goto cannot move into other block :
goto Block
{
Block:
v := 0
fmt.Println(v)
}

as it causes “goto Block jumps into block starting at …” error.

Resources:

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.