思いがけずにハマってしまったので、色々と自戒を込めて備忘録がてらにメモ。
Go言語では変数定義に色々なパターンがあるが、特に:=を用いた短縮記法がよく使われる。
この短縮記法を用いた変数定義で、割とレビューでは気付きにくい問題が発生することがある。
具体的に問題を再現したコードは以下の通り。
例: main1.go
package main
import "fmt"
func main() {
msg := "xxx"
// ifでもforでも良いが、ここでブロック.
{
msg, err := exec() // この行が落とし穴.
if err != nil {
panic(err)
}
fmt.Println("msg = " + msg) // yyyと出力される.
}
fmt.Println("msg = " + msg) // xxxと出力される.
}
func exec() (string, error) {
return "yyy", nil
}
このプログラムを実行した結果はfmt.Println()の行にコメントで書いた通りとなる。
先に落とし穴ポイントを書いておくと、今回問題になったのはこのプログラムのmsgの値はyyyになっていて欲しかったと言う点にある。
まず:=による変数定義において対象が複数の場合は全ての変数が未定義である必要が無いと言うのがポイントである。
つまりmsgが既に定義済みの変数であっても代入されるのだ。
ただし条件として最低1つは新規で変数定義される必要がある。
この場合ではerrと言う変数の変数定義が該当するが、もし仮に定義済みであればコンパイルエラーとなる。
また今回はブロック横断の変数への代入を期待したことが事態をややこしくしていた。
要するにブロック外の変数への代入のつもりがブロック内での新たな変数定義と判断されたのだ。
ちなみに以下のプログラムは先ほどのプログラムからブロックを取り除いたものだが、コンパイルエラーにならず正常に動作する。
改めてコードを見てみると、シンプルにブロック跨ぎの考慮を怠ったために今回の問題が発生したとも言える。
例2: main2.go
package main
import "fmt"
func main() {
msg := "xxx"
msg, err := exec()
if err != nil {
panic(err)
}
fmt.Println("msg = " + msg) // yyyと出力される.
}
func exec() (string, error) {
return "yyy", nil
}
尚、最初のコードで想定通りに動かしたい場合はmsgに対して:=を使わなければ良いだけである。
想定通りに動作させたコードは次の通り。
例3: main3.go
package main
import "fmt"
func main() {
msg := "xxx"
{
var err error
msg, err = exec()
if err != nil {
panic(err)
}
fmt.Println("msg = " + msg) // yyyと出力される.
}
fmt.Println("msg = " + msg) // yyyと出力される.
}
func exec() (string, error) {
return "yyy", nil
}
単純に事前に変数定義したいものをvarで定義しておいて:=ではなく=に差し替えるだけなので難しいことは無い。
もし何らかの理由でvarを使いたく無いのであれば、新しい変数を:=で定義してからmsgへ代入し直しても良い。