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

[インデックス 1350] ファイルの概要

このコミットは、Go言語の仕様書 (doc/go_spec.txt) における for ステートメントと range 句の定義を大幅に改訂したものです。特に、独立した range ステートメントを廃止し、range 機能を for ステートメントの新しい句 (RangeClause) として統合することで、ループ構造のセマンティクスをより明確かつ統一的に定義しています。また、ifforswitch ステートメントの初期化句で宣言された変数のスコープに関するルールも更新されています。

コミット

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」項目が多数存在していました。

変更の背景には、以下の点が挙げられます。

  1. ループ構造の統一と簡素化: 以前の仕様では、for ステートメントと range ステートメントが別々に存在していました。これは、C言語のような伝統的なループ、while ループに相当する条件のみのループ、そしてコレクションのイテレーションという、異なる種類のループを表現するために複数の構文が存在することを意味します。このコミットは、これらを for ステートメントの下に統合することで、言語の構文をより統一的かつ簡潔にすることを目指しました。これにより、開発者は for という単一のキーワードで様々なループパターンを表現できるようになります。
  2. range ステートメントのセマンティクスの明確化: コミット前の仕様書には、「range statement: to be defined more reasonably」(range ステートメント:より合理的に定義されるべき)という未解決の課題がありました。これは、range ステートメントの動作や構文に曖昧さや不十分な点があったことを示唆しています。このコミットは、range 句を for ステートメントの一部として再定義し、その動作(配列、マップ、文字列のイテレーション、インデックス/キーと値の取得、変数への代入ルールなど)を詳細に記述することで、この課題を解決しました。
  3. ステートメントのセマンティクスの整理: 同様に、「semantics of statements」(ステートメントのセマンティクス)も未解決の課題として挙げられていました。このコミットでは、for ループ内の変数スコープのルールを明確化するなど、ステートメント全体のセマンティクスに関する記述を整理し、この課題も解決済みとしてマークしています。
  4. 言語設計の成熟: Go言語の初期段階では、多くの設計上の決定が進行中でした。このコミットは、ループ構造に関する重要な設計上の決定が下され、言語仕様がより安定した段階に進んだことを示しています。特に、range 句が for ループの強力な機能として確立されたことは、Go言語のイディオム形成において重要な一歩となりました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念と、一般的なプログラミング言語におけるループの概念を理解しておく必要があります。

  1. Go言語の基本的な構文:
    • ステートメント (Statements): プログラムの実行可能な最小単位。変数宣言、代入、関数呼び出し、制御構造などが含まれます。
    • ブロック (Blocks): 波括弧 {} で囲まれた一連のステートメント。スコープを定義します。
    • 変数宣言と代入: var キーワードや := (短い変数宣言) を使った変数宣言、= を使った代入。
    • 式 (Expressions): 値を生成するコードの一部。
  2. ループの概念:
    • 条件付きループ (e.g., while ループ): 特定の条件が真である限り、コードブロックを繰り返し実行するループ。
    • カウンターループ (e.g., C言語の for ループ): 初期化、条件、更新の3つの要素を持つループで、通常はカウンタ変数を使って繰り返し回数を制御します。
    • イテレーション (Iteration): コレクション(配列、リスト、マップなど)の各要素を順番に処理すること。
  3. Go言語におけるスコープルール:
    • Go言語では、変数のスコープは宣言されたブロックに限定されます。このコミットでは、特に ifforswitch ステートメントの初期化句で宣言された変数のスコープが、そのステートメントのブロック内に限定されることが明記されています。
  4. Go言語のデータ構造:
    • 配列 (Arrays): 同じ型の要素が固定長で連続して格納されるデータ構造。
    • マップ (Maps): キーと値のペアを格納するデータ構造。キーは一意であり、値にアクセスするために使用されます。
    • 文字列 (Strings): 不変のバイトシーケンス。GoではUTF-8でエンコードされたテキストを扱うことが一般的です。

このコミットは、Go言語の初期の仕様策定段階における重要な変更であり、現在のGo言語の for...range ループの基礎を築いたものです。

技術的詳細

このコミットの技術的な核心は、Go言語のループ構造の統合と range 句のセマンティクスの詳細化にあります。

1. for ステートメントの拡張

以前は ForStatRangeStat が別々の文法要素でしたが、このコミットにより 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番目の識別子が対応する値。
  • = または :=:
    • :=: イテレーション変数を新しく宣言し、そのスコープは for ステートメントのブロックの終わりまでとなります。変数の型は、配列のインデックス/要素の型、またはマップのキー/値の型に推論されます。
    • =: 既存の変数にイテレーション値を代入します。この場合、イテレーション変数の型は、対応するインデックス/キーまたは要素/値の型と代入互換性がある必要があります。

3. 変数スコープルールの明確化

Declarations and scope rules, Rule 3 が更新され、ifforswitch ステートメントの初期化句で宣言された変数のスコープが、そのステートメントに関連付けられたブロック内に限定されることが明記されました。

変更前 (抜粋):

	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 != nilerr のような変数が、それぞれのステートメントのブロック外からはアクセスできないことを明確にしています。

4. マップのイテレーションに関するセマンティクス

マップのイテレーションに関する重要なセマンティクスが追加されました。

  • イテレーション中にまだ処理されていないマップエントリが削除された場合、それらは処理されません。
  • イテレーション中にマップエントリが挿入された場合の動作は、実装依存です。
  • イテレーション中にイテレーション変数に代入を行っても、その後のイテレーションには影響せず、現在のイテレーションの変数の値のみが変更されます。

これらの変更は、Go言語のループ構造をより強力で、一貫性があり、予測可能なものにするための重要なステップでした。

コアとなるコードの変更箇所

このコミットは、Go言語の仕様書である doc/go_spec.txt のみを変更しています。具体的な変更箇所は以下の通りです。

  1. Missing セクションからの削除:

    • - [ ] range statement: to be defined more reasonably
    • - [ ] semantics of statements これらの項目が未解決の課題リストから削除されました。
  2. 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 これらの項目が解決済みの課題リストに追加されました。
  3. Contents セクションからの削除:

    • - Range statements 独立した「Range statements」の章への参照が削除されました。
  4. スコープルールの更新:

    • @@ -583,7 +582,9 @@ の部分で、変数のスコープに関するルール3が更新され、if, for, switch ステートメントの初期化句で宣言された変数のスコープが、そのステートメントのブロックに限定されることが明記されました。
  5. Statement 文法の変更:

    • @@ -2369,8 +2370,7 @@ の部分で、Statement の定義から RangeStat が削除されました。
  6. For statements 章の全面的な書き換えと RangeClause の導入:

    • @@ -2589,68 +2589,99 @@ の部分がこのコミットの最も重要な変更点です。
    • ForStat の文法が ForStat = "for" [ Condition | ForClause | RangeClause ] Block . に変更され、RangeClausefor ステートメントの一部として導入されました。
    • ForClause の定義と、for ループの init ステートメント、conditionpost ステートメントに関する詳細な説明が追加されました。
    • RangeClause の文法 (RangeClause = IdentifierList ( "=" | ":=" ) "range" Expression .) と、配列やマップのイテレーションに関する詳細なセマンティクス、イテレーション変数の型推論、スコープ、マップのイテレーション中の動作(要素の削除や挿入)に関する注意点が記述されました。
    • range 句を使用した配列とマップのイテレーションの具体的なコード例が追加されました。

これらの変更は、Go言語のループ構造の設計思想を根本的に変更し、現在のGo言語の for...range ループの動作の基礎を確立しました。

コアとなるコードの解説

このコミットはGo言語の仕様書 (doc/go_spec.txt) のみを変更しており、直接的な実行可能なコードの変更は含まれていません。しかし、この仕様書の変更が、Goコンパイラやランタイムが for ループと range 句をどのように解釈し、実行するかを規定する「コアとなるコード」の設計指針となります。

変更された仕様の「コアとなるコード」の概念的な解説は以下の通りです。

  1. for ステートメントのパーシングとセマンティック分析:

    • コンパイラは、for キーワードに続く部分を解析し、それが ConditionForClause、または RangeClause のいずれであるかを識別します。
    • RangeClause が検出された場合、コンパイラは range の対象となる Expression の型(配列、マップ、ポインタ)をチェックし、IdentifierList の数(1つまたは2つ)と、:= または = の使用を検証します。
    • イテレーション変数の型が := で宣言された場合は、range の対象の型に基づいて適切に推論されます(例: 配列のインデックスは int、要素は配列の要素型)。= で代入される場合は、既存の変数の型と代入互換性があるかを確認します。
  2. range イテレーションの内部実装:

    • 配列のイテレーション: コンパイラは、配列の長さに基づいてループを生成します。内部的には、インデックス変数を0から配列の長さ-1までインクリメントし、各イテレーションで対応する要素にアクセスするコードに変換されます。
    • マップのイテレーション: マップのイテレーションは、配列よりも複雑です。マップは順序が保証されないため、イテレーションの順序は不定です。コンパイラは、マップの内部構造を走査するためのランタイム関数を呼び出すコードを生成します。このランタイム関数は、マップの各キーと値のペアを順番に(ただし順序は不定)提供します。
      • 仕様に記載されている「イテレーション中にまだ処理されていないマップエントリが削除された場合、それらは処理されません」や「イテレーション中にマップエントリが挿入された場合の動作は、実装依存です」といったルールは、ランタイムがマップの変更をどのように扱うかを規定しています。これは、イテレーション中にマップの構造が変更された場合の競合状態や予測不能な動作を防ぐための重要なセマンティクスです。
    • 文字列のイテレーション: 文字列の range は、UTF-8エンコードされた文字列の各Unicodeコードポイント(ルーン)をイテレーションします。最初の戻り値はバイトオフセット(インデックス)、2番目の戻り値はルーン値です。
  3. スコープの適用:

    • コンパイラは、ifforswitch ステートメントの初期化句で宣言された変数が、そのステートメントのブロック内でのみ有効であることを強制します。これにより、変数の意図しないリークや名前の衝突を防ぎ、コードの可読性と保守性を向上させます。

この仕様変更は、Go言語のコンパイラとランタイムが、for...range ループを効率的かつ正確に処理するための基盤を提供しました。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴: GitHub上のGo言語リポジトリのコミット履歴を直接参照しました。
  • Go言語の初期の仕様書: このコミットが変更した当時の doc/go_spec.txt の内容を分析しました。
  • Go言語の for ループと range 句に関する一般的な解説: Go言語の for ループと range 句の動作に関する一般的な知識を補完するために、Go言語のチュートリアルやブログ記事などを参考にしました。
  • Go言語の設計に関する議論: Go言語のメーリングリストやIssueトラッカーのアーカイブを検索することで、当時の設計上の決定に関する議論の背景を理解するのに役立ちました。