Label scopes

July 3, 2023

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


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Goto statements

We’ve finished the discussion of the new range-over-func feature, so let’s continue where we left off before the Go 1.23 release, with a feature you’ll rarely, if ever, use. Goto statements A “goto” statement transfers control to the statement with the corresponding label within the same function. GotoStmt = "goto" Label . goto Error Unless your only programming experience has been in the BASIC language, you’ve probably learned to eschew “goto”.


Break statements with labels

Break statements … If there is a label, it must be that of an enclosing “for”, “switch”, or “select” statement, and that is the one whose execution terminates. In other words, you can’t break to a label that labels a more deeply nested for, switch or select statement, or to one that’s completely unrelated. But that’s only intuitive, right? OuterLoop: for i = 0; i < n; i++ { for j = 0; j < m; j++ { switch a[i][j] { case nil: state = Error break OuterLoop case item: state = Found break OuterLoop } } } Speaking of labels, did you know that you can have blank labels?


Range conclusion

Today we finish up with some final notes, and a big example, from the spec section on for statements. For statements with range clause … If the iteration variables are not explicitly declared by the “range” clause, they must be preexisting. In this case, the iteration values are assigned to the respective variables as in an assignment statement. I actually discussed this case yesterday, so won’t go into it again.

Get daily content like this in your inbox!

Subscribe