Go 語言怎么解決編譯器錯(cuò)誤“err is shadowed during return”?
1介紹
在 Go 語言開發(fā)中,我們可能會遇到“錯(cuò)誤在返回時(shí)被隱藏”的錯(cuò)誤,該錯(cuò)誤在 Go 編碼時(shí)很難發(fā)現(xiàn),在 GoLand 中也只是會變量名高亮提示,只有在編譯 Go 項(xiàng)目時(shí),Go 編譯器會返回 err is shadowed during return。
本文我們介紹為什么會出現(xiàn)該錯(cuò)誤,以及我們應(yīng)該怎么解決?
2.為什么出現(xiàn)該錯(cuò)誤?
示例代碼:
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
}
輸出結(jié)果:
./main.go:18:3: err is shadowed during return
閱讀上面這段代碼,我們在編譯代碼時(shí),編譯器返回錯(cuò)誤“err is shadowed during return”。
因?yàn)楹瘮?shù) func foo() (err error)? 的返回值是具名參數(shù),其作用域是函數(shù) foo()? 的函數(shù)體,在函數(shù)體中,if? 分支使用短變量聲明的方式重新聲明變量 err?,它的作用域是 if 分支。
在 if? 分支聲明的變量 err?,它的內(nèi)存地址與外層變量 err? 不是同一個(gè)內(nèi)存地址,而在 if? 分支中使用 return? 返回的是外層變量 err?,所以 if? 分支中的變量 err? 被外層變量 err 所遮蔽,導(dǎo)致在編譯 Go 項(xiàng)目時(shí),Go 編譯器返回錯(cuò)誤“err is shadowed during return”。
3.怎么解決?
閱讀完 Part02,讀者朋友們已經(jīng)了解了錯(cuò)誤的原因。實(shí)際上,出現(xiàn)該錯(cuò)誤,歸根結(jié)底是我們沒有真正掌握 Go 的基礎(chǔ)知識。
為什么這么說呢?因?yàn)樵谖覀児娞柕臍v史文章中,關(guān)于 Go 變量聲明、作用域、函數(shù)等基礎(chǔ)知識都有介紹。
如果讀者朋友們徹底掌握這些基礎(chǔ)知識,大概率是不會遇到該錯(cuò)誤“err is shadowed during return”。
解決該錯(cuò)誤也比較簡單,錯(cuò)誤的原因是變量被遮蔽,我們通過使用不同的變量名,可以輕松規(guī)避這個(gè)錯(cuò)誤。
示例代碼:
...
func foo() (err error) {
if err1 := bar(); err1 != nil {
return
}
return nil
}
...
但是,使用不同的變量名真的解決問題了嗎?
我們運(yùn)行使用不同變量名的代碼,確實(shí) Go 編譯器沒有返回錯(cuò)誤,我們可以正常編譯 Go 項(xiàng)目。
細(xì)心的讀者朋友們可能已經(jīng)發(fā)現(xiàn),該解決方案雖然可以規(guī)避 Go 編譯器返回錯(cuò)誤,但是并沒有將錯(cuò)誤傳遞到外層變量 err。
所以,我們還需要將新變量 err1 的值賦值給外層變量 err,代碼如下:
...
func foo() (err error) {
if err1 := bar(); err1 != nil {
err = err1
return
}
return nil
}
...
現(xiàn)在,我們才算徹底解決了問題。改造后的代碼,既不會引起 Go 編譯器返回錯(cuò)誤,也可以將錯(cuò)誤信息傳遞出去。
讀者朋友們?nèi)绻写a“潔癖”,肯定覺得這么寫代碼太不優(yōu)雅了。那么,有沒有優(yōu)雅的解決方案呢?
答案是有更優(yōu)雅的解決方案,我們在講變量作用域的文章中也有講過,在具名返回值的函數(shù)中,如果在函數(shù)體不同作用域中使用同名變量,不能直接返回,而是需要在 return 后面跟上變量名。
func foo() (err error) {
if err := bar(); err != nil {
return err
}
return nil
}
閱讀上面這段代碼,我們在 if? 分支的作用域中,在 return? 后面跟上變量名 err,該方式也可以解決問題,而且比使用不同變量名的方式更優(yōu)雅。
現(xiàn)在,我們學(xué)會了兩種解決方案。但是,還沒有結(jié)束。我們示例代碼中,調(diào)用函數(shù) bar 是單返回值,在實(shí)際項(xiàng)目開發(fā)中,還會遇到調(diào)用函數(shù)是多返回值。
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
}
輸出結(jié)果:
./main.go:19:3: err is shadowed during return
閱讀上面這段代碼,調(diào)用函數(shù) bar() 是多返回值。
對于調(diào)用函數(shù)是多返回值的情況,除了我們已經(jīng)講的兩種解決方式,還有其它解決方式。
...
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
}
...
閱讀上面這段代碼,我們單獨(dú)聲明新變量 code?,而不是使用短變量的方式聲明新變量 code?,避免變量 err 也被重新聲明。
4.總結(jié)
本文我們介紹 Go 語言編譯錯(cuò)誤 err is shadowed during return 的原因和解決方案。先是介紹出現(xiàn)該錯(cuò)誤的原因,然后介紹了解決該錯(cuò)誤的三種解決方式。
需要注意的是,我們示例代碼 foo 函數(shù)是具名返回值,本文講的解決方案并不適用于匿名返回值的函數(shù)。
參考資料:
https://groups.google.com/g/golang-nuts/c/HmmZXC7KcVw?pli=1
https://go.dev/ref/spec#Short_variable_declarations