[インデックス 1346] ファイルの概要
このコミットは、Go言語の構文規則、特にfor...range
文とfor
ループのインクリメント部分における変数宣言の挙動に関する重要な変更を導入しています。具体的には、range
句での変数宣言に:=
(短い変数宣言)の使用を必須とし、for
ループのインクリメント部分での:=
の使用を禁止しています。これにより、Go言語の構文の一貫性と明確性が向上しています。
コミット
commit ae5a475e20815a01d430f8fd412d317ef4a9c5b6
Author: Ken Thompson <ken@golang.org>
Date: Mon Dec 15 13:44:27 2008 -0800
range clause must have = or :=
:= illegal in for-increment
R=r
OCL=21204
CL=21204
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ae5a475e20815a01d430f8fd412d317ef4a9c5b6
元コミット内容
このコミットは、Go言語のコンパイラと標準ライブラリ、およびテストコードに以下の変更を加えています。
range
句の構文変更:for...range
ループにおいて、イテレーション変数の宣言に=
または:=
のいずれかの代入演算子を必須としました。特に、新しい変数を宣言する場合は:=
の使用が必須となります。for
ループのインクリメント部分での:=
の禁止:for
ループの3番目の部分(インクリメント/デクリメント)で、短い変数宣言演算子:=
を使用することが禁止されました。
これらの変更は、Go言語の初期段階における構文の洗練と厳格化の一環として行われました。
変更の背景
Go言語は、その設計初期段階において、シンプルさ、効率性、そして読みやすさを追求していました。このコミットが行われた2008年12月は、Go言語が一般に公開される前の開発初期段階にあたります。この時期には、言語の構文やセマンティクスが活発に議論され、変更が加えられていました。
この変更の背景には、以下の点が考えられます。
- 構文の一貫性:
for...range
ループにおける変数宣言の構文を明確にし、他の変数宣言や代入のパターンとの一貫性を高めることが目的と考えられます。特に、新しい変数を導入する際には:=
を使うというGoの慣習をrange
ループにも適用することで、コードの意図をより明確にする狙いがあります。 - 曖昧さの排除:
for
ループのインクリメント部分で:=
を許可すると、既存の変数の再代入と新しい変数の宣言の区別が曖昧になる可能性があります。例えば、for i := 0; i < N; i := i + 1
のような記述は、i
がループ内で再宣言されているかのように見え、混乱を招く可能性があります。これを禁止することで、コードの解釈を明確にし、潜在的なバグを防ぐことができます。 - コンパイラの複雑性の軽減: 構文規則をより厳格にすることで、コンパイラがコードを解析する際の複雑性を軽減し、より効率的なコンパイルを可能にするという側面も考えられます。
これらの変更は、Go言語がその後の安定したバージョンで採用する構文の基礎を築く上で重要なステップでした。
前提知識の解説
このコミットの変更内容を理解するためには、Go言語における以下の基本的な概念を理解しておく必要があります。
1. 変数宣言と代入
Go言語には、変数を宣言し、値を代入する方法がいくつかあります。
var
キーワードによる宣言:var x int = 10 // 型と初期値を指定 var y int // 型のみ指定、ゼロ値で初期化 var z = 20 // 型推論
- 短い変数宣言 (
:=
): 関数内部でのみ使用でき、変数の宣言と初期化を同時に行います。Goコンパイラが右辺の値から型を推論します。a := 30 // int型と推論される b, c := "hello", true // 複数の変数を同時に宣言・初期化
:=
は、左辺の少なくとも1つの変数が新しい変数である場合にのみ使用できます。
2. for
ループ
Go言語のfor
ループは、C言語やJavaのfor
ループに似た形式と、while
ループに似た形式、そしてfor...range
形式があります。
-
一般的な
for
ループ:for init; condition; post { // ループ本体 }
init
: ループ開始前に一度だけ実行されるステートメント(例:i := 0
)。condition
: 各イテレーションの前に評価されるブール式。true
の場合、ループが続行されます。post
: 各イテレーションの後に実行されるステートメント(例:i++
)。
-
for...range
ループ: スライス、配列、文字列、マップ、チャネルなどのコレクションをイテレートするために使用されます。for index, value := range collection { // index と value を使用 }
- スライスや配列の場合、
index
は要素のインデックス、value
は要素の値です。 - マップの場合、
index
はキー、value
は値です。 - チャネルの場合、
value
はチャネルから受信した値です。 index
またはvalue
のいずれか一方のみが必要な場合は、もう一方をアンダースコア_
で破棄できます。
for _, value := range collection { // 値のみが必要な場合 // value を使用 } for index := range collection { // インデックス/キーのみが必要な場合 // index を使用 }
- スライスや配列の場合、
3. コンパイラの字句解析と構文解析 (Yacc/Bison)
Go言語のコンパイラは、ソースコードを機械語に変換する過程で、字句解析(Lexical Analysis)と構文解析(Syntax Analysis)を行います。
-
字句解析: ソースコードをトークン(キーワード、識別子、演算子など)のストリームに分割します。このコミットの差分にある
LCOLAS
は、:=
演算子に対応するトークンである可能性が高いです。 -
構文解析: トークンのストリームが言語の文法規則に従っているかを確認し、抽象構文木(AST)を構築します。このプロセスは、Yacc(Yet Another Compiler Compiler)やBisonのようなツールを使用して生成されたパーサーによって行われることが多く、Goコンパイラの
src/cmd/gc/go.y
ファイルはそのような文法定義ファイル(Yaccの入力ファイル)です。.y
ファイルには、文法規則が定義されており、各規則には対応するアクション(C言語のコード)が記述されています。このアクションは、その文法規則がマッチしたときに実行され、ASTノードの構築などを行います。$$
は現在の規則の左辺のセマンティック値(通常はASTノード)、$1
,$2
, ... は右辺の各要素のセマンティック値を参照します。yyerror
は構文エラーを報告するための関数です。
4. Goコンパイラの内部構造 (go.h
)
src/cmd/gc/go.h
は、Goコンパイラの内部で使用されるデータ構造や定数を定義するヘッダーファイルです。Node
構造体は、抽象構文木(AST)の各ノードを表すために使用されます。このコミットでcolas
フィールドが追加されたことは、コンパイラが:=
演算子によって生成された代入ノードを特別に識別する必要があることを示しています。
技術的詳細
このコミットは、Go言語のコンパイラ(gc
)の構文解析部分に直接的な変更を加えています。
1. go.h
の変更: colas
フィールドの追加
src/cmd/gc/go.h
ファイルにおいて、ASTノードを表すNode
構造体にuchar colas; // OAS resulting from :=
というフィールドが追加されました。
uchar
は符号なし文字型で、通常1バイトです。フラグとして使用されます。colas
という名前は、:=
演算子("colon equals")に由来すると考えられます。- このフィールドは、
OAS
(代入演算子)ノードが短い変数宣言:=
によって生成されたものであるかどうかを示すフラグとして機能します。コンパイラは、このフラグを使用して、特定のコンテキスト(例:for
ループのインクリメント部分)で:=
の使用が許可されているかどうかを判断します。
2. go.y
の変更: 構文規則の修正とエラーチェックの追加
src/cmd/gc/go.y
ファイルは、Go言語の文法を定義するYaccの入力ファイルです。
-
simple_stmt
規則の変更: 短い変数宣言(exprsym3_list_r LCOLAS expr
)に対応するアクションに$$->colas = 1;
が追加されました。これは、:=
によって生成された代入ステートメントのASTノードにcolas
フラグを設定することを意味します。これにより、後続の構文解析や意味解析の段階で、この代入が:=
によるものであることを識別できるようになります。 -
ocolas
規則の削除: 以前はocolas
という補助的な規則が存在し、LCOLAS
(:=
トークン)をオプションでマッチさせていたようです。この規則が削除され、range
句の規則でLCOLAS
が直接必須とされるようになりました。 -
orange_stmt
規則の変更:for...range
ループの構文を定義するorange_stmt
規則が修正されました。- 変更前:
exprsym3_list_r ocolas LRANGE expr
- 変更後:
exprsym3_list_r LCOLAS LRANGE expr
この変更により、range
句で新しい変数を宣言する場合(例:for k,v := range collection
)、:=
(LCOLAS
)の使用が構文的に必須となりました。以前はocolas
がオプションであったため、for k,v range collection
のような記述も許容されていた可能性がありますが、この変更により明示的な:=
が強制されます。
- 変更前:
-
for_header
規則へのエラーチェックの追加:for
ループのヘッダー部分を定義するfor_header
規則に、新しいエラーチェックが追加されました。if($5 != N && $5->colas != 0) yyerror("cannot declare in the for-increment");
$5
は、for
ループのpost
部分(インクリメント/デクリメント部分)に対応するASTノードを指します。$5 != N
は、post
部分が存在することを確認します(N
はnilノードを意味すると考えられます)。$5->colas != 0
は、post
部分のステートメントが:=
によって生成されたものであるかどうかをチェックします。- もし
post
部分が:=
による宣言であった場合、yyerror("cannot declare in the for-increment");
が呼び出され、コンパイルエラーが生成されます。これにより、「for
ループのインクリメント部分で宣言することはできない」という新しい規則が強制されます。
3. 標準ライブラリとテストコードの修正
コンパイラの構文変更に伴い、既存のGoコードも新しい規則に準拠するように修正されました。
-
src/lib/json/generic.go
: JSONライブラリ内のfor...range
ループが、for k,v range j.m
からfor k,v := range j.m
のように、明示的に:=
を使用するように変更されました。これは、新しいrange
句の構文規則に合わせた修正です。 -
src/lib/json/generic_test.go
: 同様に、JSONライブラリのテストコード内のfor...range
ループも、:=
を使用するように修正されました。 -
test/ken/range.go
: このテストファイルは、range
句の様々な使用例をテストするために存在します。このコミットでは、ファイル内のすべてのfor...range
ループが、for k range a
やfor k:v range a
から、それぞれfor k := range a
やfor k:v := range a
のように、:=
を明示的に使用するように変更されました。これは、新しい構文規則が正しく適用されていることを検証するためのテストケースの更新です。
これらの変更は、Go言語の初期段階における言語仕様の成熟と安定化に向けた重要なステップであり、現在のGo言語の構文の基礎を形成しています。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -197,6 +197,7 @@ struct Node
uchar method; // OCALLMETH name
uchar iota; // OLITERAL made from iota
uchar embedded; // ODCLFIELD embedded type
+ uchar colas; // OAS resulting from :=
// most nodes
Node* left;
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -419,6 +419,7 @@ simple_stmt:
$$ = rev($1);
$$ = colas($$, $3);
$$ = nod(OAS, $$, $3);
+ $$->colas = 1;
addtotop($$);
}
| LPRINT '(' oexpr_list ')'
@@ -554,9 +555,6 @@ compound_stmt:
popdcl();
}
-ocolas:
-|\tLCOLAS
-
orange_stmt:
osimple_stmt
| exprsym3_list_r '=' LRANGE expr
@@ -570,12 +568,12 @@ orange_stmt:
$$ = nod(ORANGE, $$, $6);
$$->etype = 0;
}
-|\texprsym3_list_r ocolas LRANGE expr
+|\texprsym3_list_r LCOLAS LRANGE expr
{
$$ = nod(ORANGE, $1, $4);
$$->etype = 1;
}
-|\texprsym3 ':' exprsym3 ocolas LRANGE expr
+|\texprsym3 ':' exprsym3 LCOLAS LRANGE expr
{
$$ = nod(OLIST, $1, $3);
$$ = nod(ORANGE, $$, $6);
@@ -592,6 +590,8 @@ for_header:
break;
}
// init ; test ; incr
+ if($5 != N && $5->colas != 0)
+ yyerror("cannot declare in the for-increment");
$$ = nod(OFOR, N, N);
$$->ninit = $1;
$$->ntest = $3;
コアとなるコードの解説
src/cmd/gc/go.h
の変更
Node
構造体へのcolas
フィールドの追加は、Goコンパイラが抽象構文木(AST)のノードレベルで、その代入が短い変数宣言:=
によって行われたものかどうかを追跡できるようにするためのものです。これは、特定のコンテキスト(この場合はfor
ループのインクリメント部分)で:=
の使用を禁止するという新しい構文規則を強制するために不可欠な情報となります。
src/cmd/gc/go.y
の変更
-
simple_stmt
における$$->colas = 1;
: これは、:=
演算子を含むsimple_stmt
(例:x := 10
)が構文解析された際に、その結果生成されるASTノード(OAS
、代入ノード)のcolas
フラグを1
に設定します。これにより、コンパイラの後の段階で、この代入が短い変数宣言によるものであることを識別できます。 -
ocolas
規則の削除とorange_stmt
の変更:ocolas
規則の削除とorange_stmt
規則におけるocolas
からLCOLAS
への直接的な変更は、for...range
ループの構文をより厳格にするものです。以前は:=
がオプションであったか、あるいは他の形式の代入も許容されていた可能性がありますが、この変更により、for...range
ループで新しい変数を宣言する際には、明示的に:=
を使用することが構文的に必須となりました。これにより、for k, v := range collection
のような形式が標準となります。 -
for_header
におけるエラーチェック: この変更は、for
ループのインクリメント部分(post
部分)で短い変数宣言:=
を使用することを禁止する核心部分です。$5
は、for
ループのpost
部分に対応するASTノードです。$5->colas != 0
は、そのpost
部分のステートメントが:=
によって生成された代入であるかどうかをチェックします。- もしそうであれば、
yyerror("cannot declare in the for-increment");
が呼び出され、コンパイルエラーが発生します。これは、for i := 0; i < 10; i := i + 1
のようなコードが不正となることを意味します。for
ループのpost
部分では、既存の変数の更新(例:i++
やi = i + 1
)のみが許可され、新しい変数の宣言は許可されません。この制限は、ループ変数のスコープとライフタイムを明確にし、コードの意図をより明確にするために導入されました。
これらの変更は、Go言語の構文解析器の挙動を直接変更し、言語の構文規則をより厳密に適用することで、コードの明確性とコンパイラの堅牢性を向上させています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の
for
ステートメントに関する仕様: https://go.dev/ref/spec#For_statements - Go言語の短い変数宣言に関する仕様: https://go.dev/ref/spec#Short_variable_declarations
- Yacc/Bisonに関する情報(一般的な構文解析器ジェネレータ):
- GNU Bison: https://www.gnu.org/software/bison/
参考にした情報源リンク
- Go言語のソースコード(GitHubリポジトリ): https://github.com/golang/go
- Go言語の初期のコミット履歴(GitHub): https://github.com/golang/go/commits/master?after=ae5a475e20815a01d430f8fd412d317ef4a9c5b6+34&branch=master
- Go言語の設計に関する議論(初期のメーリングリストなど、公開されている情報があれば)
- Go Nutsメーリングリストアーカイブ: https://groups.google.com/g/golang-nuts (このコミット当時の具体的な議論を見つけるのは難しいかもしれませんが、一般的な議論の場として)
- Go言語の歴史に関する記事やブログポスト(非公式なものも含む)
- "The Evolution of Go's
for
Loop" (仮称、関連する記事があれば) - "Understanding Go's Short Variable Declaration" (仮称、関連する記事があれば)
- "The Evolution of Go's
- Yacc/Bisonのチュートリアルや解説記事(構文解析の一般的な理解のため)
- "Lex & Yacc Tutorial" (一般的な情報源)
- "Compiler Design Principles" (コンパイラ設計の教科書)
- Go言語のコンパイラに関する技術ブログや論文(もしあれば)
- "Go Compiler Internals" (仮称)
これらの情報源は、Go言語の設計思想、コンパイラの動作、および言語仕様の進化をより深く理解するのに役立ちます。