Goでは、変数のスコープルール(スコーピング)はブロック({})によって厳密に定義されており、変数の名前はネストされたスコープでシャドウイングする可能性があります。特に、ネストされた関数、無名関数、ループ、および異なるレベルで同じ名前の変数を宣言する際に多くの罠が生じます。
Goは、コードをより読みやすくするために、スコープに関する「マジック」の動作を特に最小限に抑えています。しかし、構文の柔軟性と短縮形:=による変数の再宣言が可能なことにより、誤解を招くことがあります。
ネストされた関数やループのブロック内で、上位レベルと同じ名前の変数を宣言すると、外部の変数にはアクセスできなくなります(シャドウイングされます)。ほとんどの場合、これはコンパイラに認識されず、特にクロージャで作業する際にエラーの原因になりやすいです。もう1つの一般的なエラーは、ifブロックやfor-init内で新しい変数を宣言し、そのブロックの外でその変数にアクセスしようとすることです。
常にスコープレベルに注意を払い、ネストされたブロックや無名関数で実際に必要でない限り、同じ変数名を使用しないようにし、短い名前を避け、:=の使用に注意してください。
コード例:
package main import "fmt" func main() { x := 1 { x := 2 // main()のxをシャドウイング fmt.Println("Inner x:", x) } fmt.Println("Outer x:", x) for i := 0; i < 3; i++ { x := i // 各イテレーションで新しいxが作成されます go func() { fmt.Println("Goroutine x:", x) }() } }
この例では、外部変数xは変更されず、ブロック内に新しいxが作成されます。2番目のループでは、変数xはネストされた関数でキャプチャされ、その結果は予想外となる可能性があります。
重要なポイント:
:=は常に新しい変数を作成し、外部に既に存在する変数に名前を付けることはありません。1. シャドウイングのあるネストされたブロックで最後に印刷される変数の値は何ですか?
外部変数の値です。内部変数はブロック内でのみ存在します。
2. if/forブロック内で宣言された変数に、そのブロックの外からアクセスしようとするとどうなりますか?
コンパイラはエラーを出します:スコープ外の変数。
if true { y := 5 } fmt.Println(y) // エラー
3. ループ内で変数を使ってgoroutineを作成する際に、予期しない値を避けるにはどうすればよいですか?
変数を関数のパラメータとして渡す:
for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }
:=がすでに存在する変数を変更することを期待する(新しいものを作成します)。ループは並行処理のために複数のgoroutineを初期化しますが、クロージャ内で変数を渡さずに使用すると、すべてのgoroutineが「最後の」値で動作します。
利点:
欠点:
ループ変数をクロージャのパラメータとして渡すことで、各goroutineはそれぞれの値を取得します。
利点:
欠点: