[インデックス 13155] ファイルの概要
このコミットは、Go言語の仕様書(doc/go_spec.html
)におけるrange
ループの評価順序、特にi, x[i] = range ...
のような多重代入のケースについて、その挙動を明確化することを目的としています。これは、Go言語のセマンティクスにおける潜在的な曖昧さを解消し、開発者がより正確にコードの挙動を予測できるようにするための重要な変更です。
コミット
- コミットハッシュ:
2dde4f5d2906eab25625f6a260a2eb80be9ce572
- Author: Robert Griesemer gri@golang.org
- Date: Thu May 24 10:59:48 2012 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2dde4f5d2906eab25625f6a260a2eb80be9ce572
元コミット内容
spec: clarify evaluation order of "i, x[i] = range ..."
Part of fix for issue 3464.
R=golang-dev, rsc, mirtchovski, iant, r
CC=golang-dev
https://golang.org/cl/6246045
変更の背景
Go言語のrange
ループは、配列、スライス、文字列、マップ、チャネルをイテレートするための強力な構文です。特に、for index, element = range collection
の形式でインデックスと要素の両方を受け取ることができます。しかし、このコミット以前は、i, x[i] = range x
のように、代入の左辺にインデックス変数と、そのインデックス変数を使ってアクセスされる要素が混在する場合の評価順序について、仕様が十分に明確ではありませんでした。
この曖昧さは、issue 3464
として報告された問題の一部を解決するために取り組まれました。具体的には、range
ループのイテレーションごとに、インデックスと要素がどのように評価され、どの順序で代入されるのかが不明瞭であったため、予期せぬ挙動を引き起こす可能性がありました。このコミットは、Go言語のセマンティクスをより厳密に定義し、予測可能性を高めることを目的としています。
前提知識の解説
Go言語の多重代入と評価順序
Go言語では、複数の変数に同時に値を代入する「多重代入(multiple assignment)」が可能です。例えば、a, b = b, a
のように記述することで、2つの変数の値を効率的に交換できます。
多重代入の評価順序には、以下の重要なルールがあります。
- 右辺の評価: まず、代入演算子(
=
)の右辺にあるすべての式が、左から右へ、完全に評価されます。この際、関数呼び出しやインデックス式なども評価されます。 - 左辺の評価: 次に、代入演算子の左辺にあるオペランド(変数、インデックス式、ポインタ間接参照など)が、左から右へ、完全に評価されます。この評価には、インデックス式やセレクタにおける暗黙的なポインタ間接参照も含まれます。
- 代入の実行: 最後に、右辺で評価された値が、左辺で評価されたオペランドに、左から右へ、順番に代入されます。
この2段階の評価プロセスにより、例えばx[0], x[0] = 1, 2
のような代入では、x[0]
にまず1
が代入され、次に2
が代入されるため、最終的にx[0]
の値は2
になります。
range
ループの挙動
range
ループは、コレクション(配列、スライス、文字列、マップ、チャネル)の要素を順次取り出すための構文です。
- 配列とスライス:
for index, value := range arrayOrSlice
の形式で、インデックスと対応する要素のコピーがイテレーションごとに提供されます。 - 文字列: Unicodeコードポイントごとに、バイトオフセットとルーン(Unicodeコードポイント)が提供されます。
- マップ: キーと対応する値が提供されます。マップのイテレーション順序は定義されていません。
- チャネル: チャネルがクローズされるまで、チャネルから送信された値が提供されます。
range
ループのイテレーションにおいて、各要素はループの開始時に評価されるのではなく、イテレーションごとに評価されます。このため、ループ内でコレクションが変更された場合、その変更がイテレーションに影響を与える可能性があります。
技術的詳細
このコミットの核心は、Go言語の仕様書における「Assignments(代入)」のセクションに、range
ループにおける多重代入の具体的な評価例を追加し、その挙動を明確化した点にあります。
変更前は、多重代入の一般的なルールは記述されていましたが、i, x[i] = range x
のような特定のケースにおけるx[i]
の評価タイミングが曖昧でした。特に、x[i]
のi
がrange
ループのインデックス変数である場合、そのi
が現在のイテレーションのインデックス値に更新された後にx[i]
が評価されるのか、それともrange
ループの次のイテレーションの準備段階でx[i]
が評価されるのか、という点が不明瞭でした。
このコミットでは、以下の例を追加することで、この曖昧さを解消しています。
i = 2
x = []int{3, 5, 7}
for i, x[i] = range x { // set i, x[2] = 0, x[0]
break
}
// after this loop, i == 0 and x == []int{3, 5, 3}
この例は、range
ループの最初のイテレーションでbreak
するケースを示しています。
- 初期状態:
i = 2
,x = []int{3, 5, 7}
for i, x[i] = range x
の最初のイテレーションが開始されます。range x
は、最初の要素としてインデックス0
と値3
を生成します。- このインデックス
0
と値3
が、左辺のi
とx[i]
に代入されます。- まず、右辺の値(
0
と3
)が評価されます。 - 次に、左辺のオペランドが評価されます。このとき、
i
は現在のイテレーションのインデックス0
に更新されます。そして、x[i]
は、更新されたi
の値(0
)を用いてx[0]
として評価されます。 - 最後に、値が代入されます。
i
には0
が、x[0]
には3
が代入されます。
- まず、右辺の値(
- 結果として、
i
は0
になり、x
は[]int{3, 5, 7}
から[]int{3, 5, 3}
に変化します(x[0]
が3
に上書きされ、x[2]
が3
に上書きされる)。
この例とコメントにより、range
ループにおける多重代入の左辺の評価は、現在のイテレーションで得られたインデックス値が左辺の変数に代入された後に行われることが明確に示されました。これにより、x[i]
のような式は、そのイテレーションで新しく設定されたi
の値に基づいて評価されることが保証されます。
コアとなるコードの変更箇所
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
<!--{
"Title": "The Go Programming Language Specification",
- "Subtitle": "Version of March 17, 2012",
+ "Subtitle": "Version of May 24, 2012",
"Path": "/ref/spec"
}-->
@@ -3866,7 +3866,11 @@ x, _ = f() // ignore second value returned by f()
In the second form, the number of operands on the left must equal the number
of expressions on the right, each of which must be single-valued, and the
<i>n</i>th expression on the right is assigned to the <i>n</i>th
-operand on the left. The assignment proceeds in two phases.
+operand on the left.
+</p>
+
+<p>
+The assignment proceeds in two phases.
First, the operands of <a href="#Indexes">index expressions</a>
and <a href="#Address_operators">pointer indirections</a>
(including implicit pointer indirections in <a href="#Selectors">selectors</a>)
@@ -3885,13 +3889,20 @@ i, x[i] = 1, 2 // set i = 1, x[0] = 2
i = 0
x[i], i = 2, 1 // set x[0] = 2, i = 1
-x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] = 2 at end)
+x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)
x[1], x[3] = 4, 5 // set x[1] = 4, then panic setting x[3] = 5.
type Point struct { x, y int }\n var p *Point
x[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7
+\n+i = 2
+x = []int{3, 5, 7}
+for i, x[i] = range x { // set i, x[2] = 0, x[0]
+\tbreak
+}\n+// after this loop, i == 0 and x == []int{3, 5, 3}
</pre>
\n <p>
コアとなるコードの解説
このコミットによるdoc/go_spec.html
への変更は、主に以下の2点です。
-
仕様書のバージョン日付の更新:
"Subtitle": "Version of March 17, 2012"
から"Subtitle": "Version of May 24, 2012"
へと変更され、仕様書が更新された日付が反映されています。 -
多重代入の例の追加と修正:
-
既存の多重代入の例
x[0], x[0] = 1, 2
のコメントが// set x[0] = 1, then x[0] = 2 (so x[0] = 2 at end)
から// set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)
へと、より厳密な比較演算子==
を使用するように修正されています。これは意味的な変更ではなく、表記の改善です。 -
最も重要な変更は、
range
ループにおける多重代入の新しい例が追加されたことです。i = 2 x = []int{3, 5, 7} for i, x[i] = range x { // set i, x[2] = 0, x[0] break } // after this loop, i == 0 and x == []int{3, 5, 3}
このコードスニペットは、
range
ループの最初のイテレーションでi
とx[i]
に値が代入される際の具体的な挙動を示しています。コメント// set i, x[2] = 0, x[0]
は、range
ループが生成する最初のインデックス0
と値x[0]
(つまり3
)が、それぞれi
とx[i]
に代入されることを示唆しています。そして、ループ後の状態を示す// after this loop, i == 0 and x == []int{3, 5, 3}
というコメントが、この代入がどのように行われたかを明確にしています。この例から、
range
ループのイテレーションにおいて、まずインデックス変数i
に現在のイテレーションのインデックス値(この場合は0
)が代入され、その後にx[i]
のような式が評価されることがわかります。つまり、x[i]
の評価時には、すでにi
が新しい値に更新されているため、x[0]
が参照され、その値がx[2]
に代入されるのではなく、x[0]
に代入されることになります。
-
この変更により、Go言語のrange
ループにおける多重代入の評価順序が明確になり、特にインデックス変数と要素が相互に依存するような複雑なケースでの挙動が予測可能になりました。
関連リンク
- GitHubコミット: https://github.com/golang/go/commit/2dde4f5d2906eab25625f6a260a2eb80be9ce572
- Go Code Review (CL): http://codereview.appspot.com/6246045/
- 関連Issue:
issue 3464
(Google Codeの旧トラッカーに存在したGo言語のコンパイラテストまたは適合性に関する問題の一部)
参考にした情報源リンク
- Go言語のコミット情報:
/home/orange/Project/comemo/commit_data/13155.txt
- Google Search: "Go issue 3464"
- Google Search: "golang.org CL 6246045"
- Google Groups discussion referencing
code.google.com/p/go/issues/detail?id=3464
- appspot.com (Go Code Review)