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 the viewpoint of go/*
standard packages.)
{}
encloses a local block.
However, some local blocks aren't enclosed within {}
,
such blocks are called implicit local blocks.
The local blocks enclosed in {}
are called explicit local blocks.
The {}
in composite literals and type definitions don't form
local blocks.
if
, switch
or for
keyword
is followed by two nested local blocks. One is implicit, the other is explicit.
The explicit one is nested in the implicit one.
If such a keyword is followed by a short variable declaration,
then the variables are declared in the implicit block.
else
keyword is followed by one explicit or implicit block,
which is nested in the implicit block following its if
counterpart keyword.
If the else
keyword is followed by another if
keyword,
then the code block following the else
keyword can be implicit,
otherwise, the code block must be explicit.
select
keyword is followed by one explicit block.
case
and default
keyword is followed by one implicit block,
which is nested in the explicit block following its corresponding
switch
or select
keyword.
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.
go/*
standard packages.)
go/*
standard packages.)
(The differences to Go specification are to make the below explanations for identifier shadowing simpler.)
Here is a picture shows the block hierarchies in a program:
Code blocks are mainly used to explain allowed declaration positions and scopes of source code element identifiers.
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 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.
if
, switch
or for
keyword can be closely followed by a short variable declaration.
case
keyword in a select
control flow can be closely followed by a short variable declaration.
(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.
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.)
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: T uses
x []T // error: T uses
}
*/
// 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 p P
p = &p
p = ***********************p
***********************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]
}
Note, call fmt.Print(s)
and call fmt.Print(m)
both panic (for stack overflow).
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
*/
}
Ignoring labels, an identifier declared in an outer code block 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 namedx
.
A x
declared in a deeper block shadows
the x
s 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)
}
Another example: the following program prints
Sheep Goat
instead of Sheep Sheep
.
Please read the comments for explanations.
package main
import "fmt"
var f = func(b bool) {
fmt.Print("Goat")
}
func main() {
var f = func(b bool) {
fmt.Print("Sheep")
if b {
fmt.Print(" ")
f(!b) // The f is the package-level f.
}
}
f(true) // The f is the local f.
}
If we modify the above program as the following shown, then it will print
Sheep Sheep
.
func main() {
var f func(b bool)
f = func(b bool) {
fmt.Print("Sheep")
if b {
fmt.Print(" ")
f(!b) // The f is also the local f now.
}
}
f(true)
}
For some circumstances, when identifiers are shadowed by variables declared with short variable declarations, some new gophers may get confused about whether a variable in a short variable declaration is redeclared or newly 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 of 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 declared here actually.
// 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
// outer 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 declared 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 output = 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)
output(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 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.
주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.
reflect
표준 패키지sync
표준 패키지sync/atomic
표준 패키지