[インデックス 1350] ファイルの概要
このコミットは、Go言語の仕様書 (doc/go_spec.txt
) における for
ステートメントと range
句の定義を大幅に改訂したものです。特に、独立した range
ステートメントを廃止し、range
機能を for
ステートメントの新しい句 (RangeClause
) として統合することで、ループ構造のセマンティクスをより明確かつ統一的に定義しています。また、if
、for
、switch
ステートメントの初期化句で宣言された変数のスコープに関するルールも更新されています。
コミット
commit 30a1a8c92251941dd850d66ec434231cc1140fb3
Author: Robert Griesemer <gri@golang.org>
Date: Tue Dec 16 11:38:56 2008 -0800
language for range clause
(I have deliberately left away the forms w/ := or = and
the forms with :)
R=r
DELTA=106 (44 added, 13 deleted, 49 changed)
OCL=21192
CL=21283
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/30a1a8c92251941dd850d66ec434231cc1140fb3
元コミット内容
このコミットの元々の内容は、Go言語の for
ループに range
句を導入することです。コミットメッセージには「(I have deliberately left away the forms w/ := or = and the forms with :)」とあり、これは range
句における変数宣言の形式(:=
または =
)や、コロン(:
)を使った形式について、意図的に詳細な記述を省略していることを示唆しています。これは、当時のGo言語の仕様策定がまだ流動的であり、特定の構文の詳細が確定していなかった可能性を示しています。
変更の背景
このコミットが行われた2008年12月は、Go言語が一般に公開される直前の、活発な開発と仕様策定の時期にあたります。当時のGo言語の仕様書 (doc/go_spec.txt
) には、まだ未解決の課題や「TODO」項目が多数存在していました。
変更の背景には、以下の点が挙げられます。
- ループ構造の統一と簡素化: 以前の仕様では、
for
ステートメントとrange
ステートメントが別々に存在していました。これは、C言語のような伝統的なループ、while
ループに相当する条件のみのループ、そしてコレクションのイテレーションという、異なる種類のループを表現するために複数の構文が存在することを意味します。このコミットは、これらをfor
ステートメントの下に統合することで、言語の構文をより統一的かつ簡潔にすることを目指しました。これにより、開発者はfor
という単一のキーワードで様々なループパターンを表現できるようになります。 range
ステートメントのセマンティクスの明確化: コミット前の仕様書には、「range
statement: to be defined more reasonably」(range
ステートメント:より合理的に定義されるべき)という未解決の課題がありました。これは、range
ステートメントの動作や構文に曖昧さや不十分な点があったことを示唆しています。このコミットは、range
句をfor
ステートメントの一部として再定義し、その動作(配列、マップ、文字列のイテレーション、インデックス/キーと値の取得、変数への代入ルールなど)を詳細に記述することで、この課題を解決しました。- ステートメントのセマンティクスの整理: 同様に、「semantics of statements」(ステートメントのセマンティクス)も未解決の課題として挙げられていました。このコミットでは、
for
ループ内の変数スコープのルールを明確化するなど、ステートメント全体のセマンティクスに関する記述を整理し、この課題も解決済みとしてマークしています。 - 言語設計の成熟: Go言語の初期段階では、多くの設計上の決定が進行中でした。このコミットは、ループ構造に関する重要な設計上の決定が下され、言語仕様がより安定した段階に進んだことを示しています。特に、
range
句がfor
ループの強力な機能として確立されたことは、Go言語のイディオム形成において重要な一歩となりました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念と、一般的なプログラミング言語におけるループの概念を理解しておく必要があります。
- Go言語の基本的な構文:
- ステートメント (Statements): プログラムの実行可能な最小単位。変数宣言、代入、関数呼び出し、制御構造などが含まれます。
- ブロック (Blocks): 波括弧
{}
で囲まれた一連のステートメント。スコープを定義します。 - 変数宣言と代入:
var
キーワードや:=
(短い変数宣言) を使った変数宣言、=
を使った代入。 - 式 (Expressions): 値を生成するコードの一部。
- ループの概念:
- 条件付きループ (e.g.,
while
ループ): 特定の条件が真である限り、コードブロックを繰り返し実行するループ。 - カウンターループ (e.g., C言語の
for
ループ): 初期化、条件、更新の3つの要素を持つループで、通常はカウンタ変数を使って繰り返し回数を制御します。 - イテレーション (Iteration): コレクション(配列、リスト、マップなど)の各要素を順番に処理すること。
- 条件付きループ (e.g.,
- Go言語におけるスコープルール:
- Go言語では、変数のスコープは宣言されたブロックに限定されます。このコミットでは、特に
if
、for
、switch
ステートメントの初期化句で宣言された変数のスコープが、そのステートメントのブロック内に限定されることが明記されています。
- Go言語では、変数のスコープは宣言されたブロックに限定されます。このコミットでは、特に
- Go言語のデータ構造:
- 配列 (Arrays): 同じ型の要素が固定長で連続して格納されるデータ構造。
- マップ (Maps): キーと値のペアを格納するデータ構造。キーは一意であり、値にアクセスするために使用されます。
- 文字列 (Strings): 不変のバイトシーケンス。GoではUTF-8でエンコードされたテキストを扱うことが一般的です。
このコミットは、Go言語の初期の仕様策定段階における重要な変更であり、現在のGo言語の for...range
ループの基礎を築いたものです。
技術的詳細
このコミットの技術的な核心は、Go言語のループ構造の統合と range
句のセマンティクスの詳細化にあります。
1. for
ステートメントの拡張
以前は ForStat
と RangeStat
が別々の文法要素でしたが、このコミットにより RangeStat
は廃止され、for
ステートメントが RangeClause
を含むように拡張されました。
変更前 (抜粋):
Statement =
Declaration | LabelDecl | EmptyStat |
SimpleStat | GoStat | ReturnStat | BreakStat | ContinueStat | GotoStat |
FallthroughStat | Block | IfStat | SwitchStat | SelectStat | ForStat |
RangeStat .
ForStat = "for" [ Condition | ForClause ] Block .
RangeStat = "range" IdentifierList ":=" RangeExpression Block .
変更後 (抜粋):
Statement =
Declaration | LabelDecl | EmptyStat |
SimpleStat | GoStat | ReturnStat | BreakStat | ContinueStat | GotoStat |
FallthroughStat | Block | IfStat | SwitchStat | SelectStat | ForStat . // RangeStat が削除された
ForStat = "for" [ Condition | ForClause | RangeClause ] Block . // RangeClause が追加された
これにより、for
ステートメントは以下の3つの形式をサポートするようになりました。
- 条件のみの
for
ループ:for Condition { Block }
(C言語のwhile
に相当) for
句を持つfor
ループ:for InitStat ; Condition ; PostStat { Block }
(C言語の伝統的なfor
に相当)range
句を持つfor
ループ:for RangeClause { Block }
(コレクションのイテレーション)
2. RangeClause
の詳細な定義
RangeClause
は以下のように定義されました。
RangeClause = IdentifierList ( "=" | ":=" ) "range" Expression .
Expression
: イテレーションの対象となる式。配列、マップ、または配列/マップへのポインタである必要があります。ポインタの場合、nil
であってはなりません。IdentifierList
: イテレーション変数を宣言または代入するリスト。1つまたは2つの識別子を含みます。- 1つの識別子:
- 配列の場合: 現在のインデックス。
- マップの場合: 現在のキー。
- 2つの識別子:
- 配列の場合: 最初の識別子が現在のインデックス、2番目の識別子が対応する要素。
- マップの場合: 最初の識別子が現在のキー、2番目の識別子が対応する値。
- 1つの識別子:
=
または:=
::=
: イテレーション変数を新しく宣言し、そのスコープはfor
ステートメントのブロックの終わりまでとなります。変数の型は、配列のインデックス/要素の型、またはマップのキー/値の型に推論されます。=
: 既存の変数にイテレーション値を代入します。この場合、イテレーション変数の型は、対応するインデックス/キーまたは要素/値の型と代入互換性がある必要があります。
3. 変数スコープルールの明確化
Declarations and scope rules, Rule 3
が更新され、if
、for
、switch
ステートメントの初期化句で宣言された変数のスコープが、そのステートメントに関連付けられたブロック内に限定されることが明記されました。
変更前 (抜粋):
3. The scope of a constant or variable extends textually from
after the declaration to the end of the innermost surrounding
block.
変更後 (抜粋):
3. The scope of a constant or variable extends textually from
after the declaration to the end of the innermost surrounding
block. If the variable is declared in the init statement of an
if, for, or switch statement, the innermost surrounding block
is the block associated with the respective statement.
これは、for i := 0; i < 10; i++
の i
や、if err := someFunc(); err != nil
の err
のような変数が、それぞれのステートメントのブロック外からはアクセスできないことを明確にしています。
4. マップのイテレーションに関するセマンティクス
マップのイテレーションに関する重要なセマンティクスが追加されました。
- イテレーション中にまだ処理されていないマップエントリが削除された場合、それらは処理されません。
- イテレーション中にマップエントリが挿入された場合の動作は、実装依存です。
- イテレーション中にイテレーション変数に代入を行っても、その後のイテレーションには影響せず、現在のイテレーションの変数の値のみが変更されます。
これらの変更は、Go言語のループ構造をより強力で、一貫性があり、予測可能なものにするための重要なステップでした。
コアとなるコードの変更箇所
このコミットは、Go言語の仕様書である doc/go_spec.txt
のみを変更しています。具体的な変更箇所は以下の通りです。
-
Missing
セクションからの削除:- [ ] range statement: to be defined more reasonably
- [ ] semantics of statements
これらの項目が未解決の課題リストから削除されました。
-
Closed
セクションへの追加:+ [x] semantics of statements - we just need to fill in the language, the semantics is mostly clear
+ [x] range statement: to be defined more reasonably
これらの項目が解決済みの課題リストに追加されました。
-
Contents
セクションからの削除:- Range statements
独立した「Range statements」の章への参照が削除されました。
-
スコープルールの更新:
@@ -583,7 +582,9 @@
の部分で、変数のスコープに関するルール3が更新され、if
,for
,switch
ステートメントの初期化句で宣言された変数のスコープが、そのステートメントのブロックに限定されることが明記されました。
-
Statement
文法の変更:@@ -2369,8 +2370,7 @@
の部分で、Statement
の定義からRangeStat
が削除されました。
-
For statements
章の全面的な書き換えとRangeClause
の導入:@@ -2589,68 +2589,99 @@
の部分がこのコミットの最も重要な変更点です。ForStat
の文法がForStat = "for" [ Condition | ForClause | RangeClause ] Block .
に変更され、RangeClause
がfor
ステートメントの一部として導入されました。ForClause
の定義と、for
ループのinit
ステートメント、condition
、post
ステートメントに関する詳細な説明が追加されました。RangeClause
の文法 (RangeClause = IdentifierList ( "=" | ":=" ) "range" Expression .
) と、配列やマップのイテレーションに関する詳細なセマンティクス、イテレーション変数の型推論、スコープ、マップのイテレーション中の動作(要素の削除や挿入)に関する注意点が記述されました。range
句を使用した配列とマップのイテレーションの具体的なコード例が追加されました。
これらの変更は、Go言語のループ構造の設計思想を根本的に変更し、現在のGo言語の for...range
ループの動作の基礎を確立しました。
コアとなるコードの解説
このコミットはGo言語の仕様書 (doc/go_spec.txt
) のみを変更しており、直接的な実行可能なコードの変更は含まれていません。しかし、この仕様書の変更が、Goコンパイラやランタイムが for
ループと range
句をどのように解釈し、実行するかを規定する「コアとなるコード」の設計指針となります。
変更された仕様の「コアとなるコード」の概念的な解説は以下の通りです。
-
for
ステートメントのパーシングとセマンティック分析:- コンパイラは、
for
キーワードに続く部分を解析し、それがCondition
、ForClause
、またはRangeClause
のいずれであるかを識別します。 RangeClause
が検出された場合、コンパイラはrange
の対象となるExpression
の型(配列、マップ、ポインタ)をチェックし、IdentifierList
の数(1つまたは2つ)と、:=
または=
の使用を検証します。- イテレーション変数の型が
:=
で宣言された場合は、range
の対象の型に基づいて適切に推論されます(例: 配列のインデックスはint
、要素は配列の要素型)。=
で代入される場合は、既存の変数の型と代入互換性があるかを確認します。
- コンパイラは、
-
range
イテレーションの内部実装:- 配列のイテレーション: コンパイラは、配列の長さに基づいてループを生成します。内部的には、インデックス変数を0から配列の長さ-1までインクリメントし、各イテレーションで対応する要素にアクセスするコードに変換されます。
- マップのイテレーション: マップのイテレーションは、配列よりも複雑です。マップは順序が保証されないため、イテレーションの順序は不定です。コンパイラは、マップの内部構造を走査するためのランタイム関数を呼び出すコードを生成します。このランタイム関数は、マップの各キーと値のペアを順番に(ただし順序は不定)提供します。
- 仕様に記載されている「イテレーション中にまだ処理されていないマップエントリが削除された場合、それらは処理されません」や「イテレーション中にマップエントリが挿入された場合の動作は、実装依存です」といったルールは、ランタイムがマップの変更をどのように扱うかを規定しています。これは、イテレーション中にマップの構造が変更された場合の競合状態や予測不能な動作を防ぐための重要なセマンティクスです。
- 文字列のイテレーション: 文字列の
range
は、UTF-8エンコードされた文字列の各Unicodeコードポイント(ルーン)をイテレーションします。最初の戻り値はバイトオフセット(インデックス)、2番目の戻り値はルーン値です。
-
スコープの適用:
- コンパイラは、
if
、for
、switch
ステートメントの初期化句で宣言された変数が、そのステートメントのブロック内でのみ有効であることを強制します。これにより、変数の意図しないリークや名前の衝突を防ぎ、コードの可読性と保守性を向上させます。
- コンパイラは、
この仕様変更は、Go言語のコンパイラとランタイムが、for...range
ループを効率的かつ正確に処理するための基盤を提供しました。
関連リンク
- Go言語公式ドキュメント: Go言語の最新の仕様は、常に公式ドキュメントで確認できます。
- Go言語の歴史: Go言語の設計思想や進化について学ぶことができます。
- Go at Google: Language Design in the Service of Software Engineering (Robert Griesemerによるスライド)
- A brief history of Go
参考にした情報源リンク
- Go言語のコミット履歴: GitHub上のGo言語リポジトリのコミット履歴を直接参照しました。
- Go言語の初期の仕様書: このコミットが変更した当時の
doc/go_spec.txt
の内容を分析しました。 - Go言語の
for
ループとrange
句に関する一般的な解説: Go言語のfor
ループとrange
句の動作に関する一般的な知識を補完するために、Go言語のチュートリアルやブログ記事などを参考にしました。 - Go言語の設計に関する議論: Go言語のメーリングリストやIssueトラッカーのアーカイブを検索することで、当時の設計上の決定に関する議論の背景を理解するのに役立ちました。