1介绍
在 Go 语言开发中,我们可能会遇到“错误在返回时被隐藏”的错误,该错误在 Go 编码时很难发现,在 GoLand 中也只是会变量名高亮提示,只有在编译 Go 项目时,Go 编译器会返回 err is shadowed during return。
本文我们介绍为什么会出现该错误,以及我们应该怎么解决?
2.为什么出现该错误?
示例代码:
package main
import (
"errors"
"log"
)
func main() {
err := foo()
if err != nil {
log.Printf("err=%v\n", err)
return
}
}
func foo() (err error) {
if err := bar(); err != nil {
return // Compiler reports: err is shadowed during return
}
return nil
}
func bar() error {
err := errors.New("this is bar's err")
return err
}
输出结果:
./main.go:18:3: err is shadowed during return
阅读上面这段代码,我们在编译代码时,编译器返回错误“err is shadowed during return”。
因为函数 func foo() (err error) 的返回值是具名参数,其作用域是函数 foo() 的函数体,在函数体中,if 分支使用短变量声明的方式重新声明变量 err,它的作用域是 if 分支。
在 if 分支声明的变量 err,它的内存地址与外层变量 err 不是同一个内存地址,而在 if 分支中使用 return 返回的是外层变量 err,所以 if 分支中的变量 err 被外层变量 err 所遮蔽,导致在编译 Go 项目时,Go 编译器返回错误“err is shadowed during return”。
3.怎么解决?
阅读完 Part02,读者朋友们已经了解了错误的原因。实际上,出现该错误,归根结底是我们没有真正掌握 Go 的基础知识。
为什么这么说呢?因为在我们公众号的历史文章中,关于 Go 变量声明、作用域、函数等基础知识都有介绍。
如果读者朋友们彻底掌握这些基础知识,大概率是不会遇到该错误“err is shadowed during return”。
解决该错误也比较简单,错误的原因是变量被遮蔽,我们通过使用不同的变量名,可以轻松规避这个错误。
示例代码:
...
func foo() (err error) {
if err1 := bar(); err1 != nil {
return
}
return nil
}
...
但是,使用不同的变量名真的解决问题了吗?
我们运行使用不同变量名的代码,确实 Go 编译器没有返回错误,我们可以正常编译 Go 项目。
细心的读者朋友们可能已经发现,该解决方案虽然可以规避 Go 编译器返回错误,但是并没有将错误传递到外层变量 err。
所以,我们还需要将新变量 err1 的值赋值给外层变量 err,代码如下:
...
func foo() (err error) {
if err1 := bar(); err1 != nil {
err = err1
return
}
return nil
}
...
现在,我们才算彻底解决了问题。改造后的代码,既不会引起 Go 编译器返回错误,也可以将错误信息传递出去。
读者朋友们如果有代码“洁癖”,肯定觉得这么写代码太不优雅了。那么,有没有优雅的解决方案呢?
答案是有更优雅的解决方案,我们在讲变量作用域的文章中也有讲过,在具名返回值的函数中,如果在函数体不同作用域中使用同名变量,不能直接返回,而是需要在 return 后面跟上变量名。
func foo() (err error) {
if err := bar(); err != nil {
return err
}
return nil
}
阅读上面这段代码,我们在 if 分支的作用域中,在 return 后面跟上变量名 err,该方式也可以解决问题,而且比使用不同变量名的方式更优雅。
现在,我们学会了两种解决方案。但是,还没有结束。我们示例代码中,调用函数 bar 是单返回值,在实际项目开发中,还会遇到调用函数是多返回值。
package main
import (
"errors"
"log"
)
func main() {
err := foo()
if err != nil {
log.Printf("err=%v\n", err)
return
}
}
func foo() (err error) {
if code, err := bar(); err != nil {
log.Printf("code=%v err=%v\n", code, err)
return // Compiler reports: err is shadowed during return
}
return nil
}
func bar() (int, error) {
err := errors.New("this is bar's err")
return 200, err
}
输出结果:
./main.go:19:3: err is shadowed during return
阅读上面这段代码,调用函数 bar() 是多返回值。
对于调用函数是多返回值的情况,除了我们已经讲的两种解决方式,还有其它解决方式。
...
func foo() (err error) {
var code int
if code, err = bar(); err != nil {
log.Printf("code=%v err=%v\n", code, err)
return
}
return nil
}
...
阅读上面这段代码,我们单独声明新变量 code,而不是使用短变量的方式声明新变量 code,避免变量 err 也被重新声明。
4.总结
本文我们介绍 Go 语言编译错误 err is shadowed during return 的原因和解决方案。先是介绍出现该错误的原因,然后介绍了解决该错误的三种解决方式。
需要注意的是,我们示例代码 foo 函数是具名返回值,本文讲的解决方案并不适用于匿名返回值的函数。
参考资料:
https://groups.google.com/g/golang-nuts/c/HmmZXC7KcVw?pli=1
https://go.dev/ref/spec#Short_variable_declarations