Last Monday we had a great time on the Boldly Go: Live livestream, as we built a simple linter by following an online tutorial. For the next live stream, I intend to build a real linter, to solve some real world problems on one of my open source projects. Normally, I would do that today, but my office is undergoing some serious rennovation (three walls and the ceiling are being removed and replaced), so I don’t have anywhere to record from. So I’m taking a week off from live streaming, and will be back at it next Monday (if construction completes when planned). Be sure to subscribe to the channel so you’re notified when the livestream begins.
Labels are a little-used feature of Go, so there’s a non-zero chance you’ve never used or even seen one. But they are useful in very specific situations, and they have some rather unique scoping rules.
Label Scopes
Labels are declared by labeled statements and are used in the “break”, “continue”, and “goto” statements. It is illegal to define a label that is never used. In contrast to other identifiers, labels are not block scoped and do not conflict with identifiers that are not labels. The scope of a label is the body of the function in which it is declared and excludes the body of any nested function.
There are two interesting things I want to call out here:
“It is illegal to define a label that is never used.” However, you can create all the blank labels you want. Not that they serve any purpose. LOL But I’ve created a video about this if you’re interested.
More important, though, “The scope of a label is the body of the function in which it is declared and excludes the body of any nested function.” This in essence means you can’t close over a label, like you can a variable. Let me demonstrate with a contrived example.
First, a valid version of the function (See it in the playground):
// countOccurrences counts the number of times needle occurs within haystack.
// If it encounters the string `$stop` in haystack, it aborts calculation early.
func countOccurrences(needle string, haystack [][]string) int {
var count int
stop:
for _, x := range haystack {
check := func(needle, word string) bool {
return needle == word
}
for _, y := range x {
if y == "$stop" {
break stop
}
if check(needle, y) {
count++
}
}
}
return count
}
We might be tempted to combine the stop-word check with the needle check in the check function, but this won’t work:
func countOccurrences(needle string, haystack [][]string) int {
var count int
stop:
for _, x := range haystack {
check := func(needle, word string) bool {
if word == "$stop" {
break stop
}
return needle == word
}
for _, y := range x {
if check(needle, y) {
count++
}
}
}
return count
}
This version generates two errors:
./prog.go:18:1: label stop defined and not used
./prog.go:22:11: break label not defined: stop
The root cause is the same: The reference to the stop label is outside the scope of its declaration, by virtue of being within an inline function body.
Quotes from The Go Programming Language Specification Version of December 15, 2022