名前付き戻り値が内側のブロックで:=によりシャドーイングされる
名前付き戻り値の変数と同じ名前をif文などの内側のブロックで:=を使って再宣言すると、それは別の新しいローカル変数になります。裸のreturnではブロック外の元の名前付き戻り値の値が返され、内側で設定した値は反映されません。
なぜエラーが出ないのか
出力: 0
(エラーなし)- Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: 0- 実際の出力 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 package main 2 3 import "fmt" 4 5 func compute(flag bool) (result int) { 6 if flag { 7 result := 42 _ = result ^ 8 } 9 return 10 } 11 12 func main() { 13 fmt.Println(compute(true)) 14 }
if文の中でresult := 42と書くと、それは名前付き戻り値とは別の新しいローカル変数になります。外側のresultに反映したいなら := ではなく = で代入する必要があります。
直し方: result := 42
_ = result を result = 42 にします。
パターン2
1 package main 2 3 import "fmt" 4 5 func calc(flag bool) (total int) { 6 if flag { 7 total := 100 _ = total ^ 8 } 9 return 10 } 11 12 func main() { 13 fmt.Println(calc(true)) 14 }
:=による再宣言はブロックスコープに閉じた別の変数を作るため、ブロックを抜けると外側のtotalは変更されないまま残ります。
直し方: total := 100
_ = total を total = 100 にします。
パターン3
1 package main 2 3 import "fmt" 4 5 func process(flag bool) (value int) { 6 if flag { 7 value := 7 _ = value ^ 8 } 9 return 10 } 11 12 func main() { 13 fmt.Println(process(true)) 14 }
裸のreturnはブロック外の名前付き戻り値の現在値を返すため、内側だけで完結する:=での再宣言は反映されません。
直し方: value := 7
_ = value を value = 7 にします。
パターン4
1 package main 2 3 import "fmt" 4 5 func build(flag bool) (count int) { 6 if flag { 7 count := 5 _ = count ^ 8 } 9 return 10 } 11 12 func main() { 13 fmt.Println(build(true)) 14 }
同じ名前だからといって自動的に外側の変数を指すわけではなく、:=は常に新しい変数を作る操作です。
直し方: count := 5
_ = count を count = 5 にします。
パターン5
1 package main 2 3 import "fmt" 4 5 func run(flag bool) (score int) { 6 if flag { 7 score := 99 _ = score ^ 8 } 9 return 10 } 11 12 func main() { 13 fmt.Println(run(true)) 14 }
意図した通りに反映させるには、内側のブロックでも=を使って外側の名前付き戻り値に直接代入する必要があります。
直し方: score := 99
_ = score を score = 99 にします。
よくある誤解
「関数内のどこで同じ名前の変数に値を入れても、裸のreturnはその最新の値を返してくれるはず」という思い込みは誤りです。:=による再宣言はブロックスコープに閉じた別変数を作るため、内側のブロックを抜けた時点で外側の名前付き戻り値は変更されないまま残ります。
まとめ
名前付き戻り値が内側のブロックで:=によりシャドーイングされるは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。