Code Blocks and Identifier Scopes

This article will explain the code blocks and identifier scopes in Go.

(Please note, the definitions of code block hierarchies in this article are a little different from Go specification.)

Code Blocks

In a Go project, there are four kinds of code blocks (also called blocks later): Some keywords in all kinds of control flows are followed by some implicit code blocks.

The local blocks which aren't nested in any other local blocks are called top-level (or package-level) local blocks. Top-level local blocks are all function bodies.

Note, the input parameters and output results of a function are viewed as being declared in explicit body code block of the function, even if their declarations stay out of the pair of braces enclosing the function body block.

Code block hierarchies:

(The differences to Go specification are to make the explanation of identifier shadowing simpler below.)

Here is a picture shows the block hierarchies in a program:
code block hierarchies

Code blocks are mainly used to explain allowed declaration positions and scopes of source code element identifiers.

Source Code Element Declaration Places

There are six kinds of source code elements which can be declared:

Labels are used in the break, continue, and goto statements.

A declaration binds a non-blank identifier to a source code element (constant, type, variable, function, label, or package). In other words, in the declaration, the declared source code element is named as the non-blank identifier. After the declaration, we can use the non-blank identifier to represent the declared source code element.

The following table shows which code blocks all kinds of source code elements can be directly declared in:
the universe block package blocks file blocks local blocks
predeclared (built-in elements) (1) Yes
package imports Yes
defined types and type alias (non-builtin) Yes Yes Yes
named constants (non-builtin) Yes Yes Yes
variables (non-builtin) (2) Yes Yes Yes
functions (non-builtin) Yes Yes
labels Yes

(1) predeclared elements are documented in builtin standard package.
(2) excluding struct field variables.

So, Please note,

(BTW, the go/* standard packages think file code blocks can only contain package import declarations.)

The source code elements declared in package blocks but outside of any local blocks are called package-level source code elements. Package-level source code elements can be named constants, variables, functions, defined types, or type aliases.

Scopes of Declared Source Code Elements

The scope of a declared identifier means the identifiable range of the identifier (or visible range).

Without considering identifier shadowing which will be explained in the last section of the current article, the scope definitions for the identifiers of all kinds of source code elements are listed below.

Blank identifiers have no scopes.

(Note, the predeclared iota is only visible in constant declarations.)

You may have noticed the minor difference of identifier scope definitions between local type definitions and local variables, local constants and local type aliases. The difference means a defined type may be able to reference itself in its declaration. Here is an example to show the difference.
package main

func main() {
	// var v int = v   // error: v is undefined
	// const C int = C // error: C is undefined
	/*
	type T = struct {
		*T // error: invalid recursive type alias T
	}
	*/

	// Following type definitions are all valid.
	type T struct {
		*T
		x []T
	}
	type A [5]*A
	type S []S
	type M map[int]M
	type F func(F) F
	type Ch chan Ch
	type P *P

	// ...
	var s = make(S, 3)
	s[0] = s
	s = s[0][0][0][0][0][0][0][0]

	var m = M{}
	m[1] = m
	m = m[1][1][1][1][1][1][1][1]

	var p P
	p = &p
	p = ***********************p
}

And the scope difference between package-level and local declarations:
package main

// Here the two identifiers at each line are the
// same one. The right ones are both not the
// predeclared identifiers. Instead, they are
// same as respective left one. So the two
// lines both fail to compile.
/*
const iota = iota // error: constant definition loop
var true = true   // error: typechecking loop
*/

var a = b // can reference variables declared later
var b = 123

func main() {
	// The identifiers at the right side in the
	// next two lines are the predeclared ones.
	const iota = iota // ok
	var true = true   // ok
	_ = true

	// The following lines fail to compile, for
	// c references a later declared variable d.
	/*
	var c = d
	var d = 123
	_ = c
	*/
}

Identifier Shadowing

Ignoring labels, an identifier declared in an outer code blocks can be shadowed by the same identifier declared in code blocks nested (directly or indirectly) in the outer code block.

Labels can’t be shadowed.

If an identifier is shadowed, its scope will exclude the scopes of its shadowing identifiers.

Below is an interesting example. The code contains 6 declared variables named x. A x declared in a deeper block shadows the xs declared in shallower blocks.
package main

import "fmt"

var p0, p1, p2, p3, p4, p5 *int
var x = 9999 // x#0

func main() {
	p0 = &x
	var x = 888  // x#1
	p1 = &x
	for x := 70; x < 77; x++ {  // x#2
		p2 = &x
		x := x - 70 //  // x#3
		p3 = &x
		if x := x - 3; x > 0 { // x#4
			p4 = &x
			x := -x // x#5
			p5 = &x
		}
	}

	// 9999 888 77 6 3 -3
	fmt.Println(*p0, *p1, *p2, *p3, *p4, *p5)
}

For some circumstances, when identifiers are shadowed by variables declared with short variable declarations, some new gophers may get confused on whether or not a variable in a short variable declaration is new declared. The following example (which has bugs) shows the famous trap in Go. Almost every gopher has ever fallen into the trap in the early days using Go.
package main

import "fmt"
import "strconv"

func parseInt(s string) (int, error) {
	n, err := strconv.Atoi(s)
	if err != nil {
		// Some new gophers may think err is an
		// already declared variable in the following
		// short variable declaration. However, both
		// b and err are new declareds here in fact.
		// The new declared err variable shadows the
		// err variable declared above.
		b, err := strconv.ParseBool(s)
		if err != nil {
			return 0, err
		}

		// If execution goes here, some new gophers
		// might expect a nil error will be returned.
		// But in fact, the outer non-nil error will
		// be returned instead, for the scope of the
		// inner err variable ends at the end of the
		// first if-clause.
		if b {
			n = 1
		}
	}
	return n, err
}

func main() {
	fmt.Println(parseInt("TRUE"))
}
The output:
1 strconv.Atoi: parsing "TRUE": invalid syntax

Go only has 25 keywords. Keywords can't be used as identifiers. Many familiar words in Go are not keywords, such as int, bool, string, len, cap, nil, etc. They are just predeclared (built-in) identifiers. These predeclared identifiers are declared in the universe block, so custom defined identifiers can shadow them. Here is a weird example in which many predeclared identifiers are shadowed. Its compiles and runs okay.
package main

import (
	"fmt"
)

// Shadows the built-in function identifier "len".
const len = 3
// Shadows the built-in const identifier "true".
var true = 0
// Shadows the built-in variable identifier "nil".
type nil struct {}
// Shadows the built-in type identifier "int".
func int(){}

func main() {
	fmt.Println("a weird program")
	var print = fmt.Println

	// Shadows the package import "fmt".
	var fmt = [len]nil{{}, {}, {}}
	// Sorry, "len" is a constant.
	// var n = len(fmt)
	// Use the built-in cap function instead, :(
	var n = cap(fmt)

	// The "for" keyword is followed by one
	// implicit local code block and one explicit
	// local code block. The iteration variable
	// "true" shadows the package-level variable
	// "true" declared above.
	for true := 0; true < n; true++ {
		// Shadows the built-in const "false".
		var false = fmt[true]
		// The new declared "true" variable
		// shadows the iteration variable "true".
		var true = true+1
		// Sorry, "fmt" is an array, not a package.
		// fmt.Println(true, false)
		print(true, false)
	}
}
The output:
a weird program
1 {}
2 {}
3 {}

Yes, this example is extreme. It contains many bad practices. Identifier shadowing is useful, but please don't abuse it.


The Go 101 project is hosted on both Github and Gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.

Support Go 101 by playing Tapir's games. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212