Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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つの変数の値を効率的に交換できます。

多重代入の評価順序には、以下の重要なルールがあります。

  1. 右辺の評価: まず、代入演算子(=)の右辺にあるすべての式が、左から右へ、完全に評価されます。この際、関数呼び出しやインデックス式なども評価されます。
  2. 左辺の評価: 次に、代入演算子の左辺にあるオペランド(変数、インデックス式、ポインタ間接参照など)が、左から右へ、完全に評価されます。この評価には、インデックス式やセレクタにおける暗黙的なポインタ間接参照も含まれます。
  3. 代入の実行: 最後に、右辺で評価された値が、左辺で評価されたオペランドに、左から右へ、順番に代入されます。

この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]irangeループのインデックス変数である場合、その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が、左辺のix[i]に代入されます。
    • まず、右辺の値(03)が評価されます。
    • 次に、左辺のオペランドが評価されます。このとき、iは現在のイテレーションのインデックス0に更新されます。そして、x[i]は、更新されたiの値(0)を用いて x[0]として評価されます。
    • 最後に、値が代入されます。iには0が、x[0]には3が代入されます。
  • 結果として、i0になり、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点です。

  1. 仕様書のバージョン日付の更新: "Subtitle": "Version of March 17, 2012" から "Subtitle": "Version of May 24, 2012" へと変更され、仕様書が更新された日付が反映されています。

  2. 多重代入の例の追加と修正:

    • 既存の多重代入の例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ループの最初のイテレーションでix[i]に値が代入される際の具体的な挙動を示しています。コメント// set i, x[2] = 0, x[0]は、rangeループが生成する最初のインデックス0と値x[0](つまり3)が、それぞれix[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ループにおける多重代入の評価順序が明確になり、特にインデックス変数と要素が相互に依存するような複雑なケースでの挙動が予測可能になりました。

関連リンク

参考にした情報源リンク

  • 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)