part 1.
短变量声明不总是会声明一个新变量。
e.g.
func main() {
var a = 1
a, b := 2, 3
fmt.Println(a, b)
}
按照我们对声明语句的传统理解,上面第二行的多变量短声明语句重新声明了变量 a 和 b,我们用下面代码来确认一下:
func main() {
var a = 1
fmt.Println(&a) // 0xc00000a0b0
a, b := 2, 3
fmt.Println(&a) // 0xc00000a0b0
fmt.Println(a, b)
}
从输出结果来看,我们对声明语句的传统理解似乎 “失效” 了。多变量短声明语句 (multi-variable short declaration) 并未重新声明一个新变量 a,它只是给之前已经声明了的变量 a 做了重新赋值。
这是一个典型的由于认知偏差而形成的 “陷阱”。Go 规范针对此 “陷阱” 有明确的说明:在同一个代码块 (block) 中,使用多变量短声明语句重新声明已经声明过的变量时,短变量声明语句不会为该变量声明一个新变量,而只是会对其做重新赋值。
如果不在同一个代码块 (block) 中呢?
part 2.
在不同代码块 (block) 层次上使用多变量短声明形式会带来 “难以发现” 的变量遮蔽问题。
e.g.
var cwd string
func init() {
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
log.Printf("Working directory = %s", cwd)
}
虽然 cwd 在外部已经声明过,但是 := 语句还是将 cwd 和 err 重新声明为新的局部变量。因为内部声明的 cwd 将屏蔽外部的声明,因此上面的代码并不会正确更新包级声明的 cwd 变量。
有许多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明 err 变量,来避免使用 := 的简短声明方式:
var cwd string
func init() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
根本原因是因为,当编译器遇到一个名字引用时,它会对其定义进行查找,查找过程从最内层的词法域向全局的作用域进行。
如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。
在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问 (也就是说,如果一个标识符被遮挡了,则它的作用域将不包括遮挡它的标识符的作用域)。
e.g.
func foo() (int, error) {
return 11, nil
}
func bar() (int, error) {
return 21, errors.New("error in bar")
}
func main() {
var err error
defer func() {
if err != nil {
println("error in defer:", err.Error())
}
}()
a, err := foo()
if err != nil {
return
}
println("a =", a)
if a == 11 {
b, err := bar()
if err != nil {
return
}
println("b =", b)
}
println("no error occurs")
}
对于上面这个示例,我们期待输出下面结果:
a = 11
error in defer: error in bar
实际:
a = 11
b, err := bar() 这行代码后会误以为 err 不会被重新声明为一个新变量,仅会做赋值操作,就像前面 part 1 短声明变量的“陷阱” 中描述的那样。
但实际上,由于不在同一个代码块 (block) 中,编译器没有在同一代码块里找到与 b, err := bar() 这行代码中 err 同名的变量,因此会声明一个新 err 变量,该 err 变量也就 “顺理成章” 地遮蔽了 main 函数代码块中的 err 变量。
同上,修正这个问题的方法有很多,但最直接的方法就是去掉 if 代码块中的多变量短声明形式并提前单独声明变量 b。
e.g.
// 同理 shadow(), shadow2() 都是错误示例
func shadow() (err error) {
x, err := check1() // x是新创建变量,err是被赋值
if err != nil {
return // 正确返回err
}
if y, err := check2(x); err != nil { // y和if语句中err被创建
return // if语句中的err覆盖外面的err,所以错误的返回nil!
} else {
fmt.Println(y)
}
return
}
func shadow2() (err error) {
if err := check3(); err != nil {
return
}
return
}
func check1() (int, error) {
return 1, nil
}
func check2(x int) (int, error) {
return 0, errors.New("error in bar")
}
func check3() error {
return errors.New("error in bar")
}
func main() {
fmt.Println(shadow())
fmt.Println(shadow2())
}