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

[インデックス 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言語のコンパイラと標準ライブラリ、およびテストコードに以下の変更を加えています。

  1. range句の構文変更: for...rangeループにおいて、イテレーション変数の宣言に=または:=のいずれかの代入演算子を必須としました。特に、新しい変数を宣言する場合は:=の使用が必須となります。
  2. 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 afor k:v range aから、それぞれfor k := range afor 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の変更

  1. simple_stmtにおける$$->colas = 1;: これは、:=演算子を含むsimple_stmt(例: x := 10)が構文解析された際に、その結果生成されるASTノード(OAS、代入ノード)のcolasフラグを1に設定します。これにより、コンパイラの後の段階で、この代入が短い変数宣言によるものであることを識別できます。

  2. ocolas規則の削除とorange_stmtの変更: ocolas規則の削除とorange_stmt規則におけるocolasからLCOLASへの直接的な変更は、for...rangeループの構文をより厳格にするものです。以前は:=がオプションであったか、あるいは他の形式の代入も許容されていた可能性がありますが、この変更により、for...rangeループで新しい変数を宣言する際には、明示的に:=を使用することが構文的に必須となりました。これにより、for k, v := range collectionのような形式が標準となります。

  3. 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言語のソースコード(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" (仮称、関連する記事があれば)
  • Yacc/Bisonのチュートリアルや解説記事(構文解析の一般的な理解のため)
    • "Lex & Yacc Tutorial" (一般的な情報源)
    • "Compiler Design Principles" (コンパイラ設計の教科書)
  • Go言語のコンパイラに関する技術ブログや論文(もしあれば)
    • "Go Compiler Internals" (仮称)

これらの情報源は、Go言語の設計思想、コンパイラの動作、および言語仕様の進化をより深く理解するのに役立ちます。