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

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

このコミットは、Go言語のコンパイラ(6g、当時のamd64アーキテクチャ向けコンパイラ)において、配列の比較に関するセマンティクスを変更するものです。具体的には、配列の比較をリテラルな nil との等価性 (==) または非等価性 (!=) のチェックに限定し、それ以外の比較(例: 大小比較)を不正な操作としてコンパイルエラーにする変更が加えられました。

コミット

commit a91a0a6a7a319425d29a47ab7900c432d6f648e4
Author: Ken Thompson <ken@golang.org>
Date:   Fri Dec 19 14:26:52 2008 -0800

    array compare (except = != nil) are illegal
    
    R=r
    OCL=21637
    CL=21637

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a91a0a6a7a319425d29a47ab7900c432d6f648e4

元コミット内容

array compare (except = != nil) are illegal

変更の背景

このコミットは、Go言語の非常に初期の段階(2008年12月)に行われたもので、Go言語の設計思想と型システムの進化を反映しています。当時のGo言語では、配列の比較に関する明確なセマンティクスがまだ確立されていなかった可能性があります。

Go言語の設計哲学の一つに「シンプルさと明示性」があります。複合型(配列、構造体など)の比較において、特に順序比較(<, > など)は、その意味が曖昧になりがちです。例えば、2つの配列 [1, 5][2, 3] を比較してどちらが大きいかを判断する場合、要素ごとの比較順序や、どの要素を優先するかによって結果が変わる可能性があります。このような曖昧さを避けるため、Go言語は複合型の比較に対して厳格なルールを設ける傾向があります。

このコミットは、配列の比較を nil との等価性/非等価性チェックに限定することで、配列の比較に関する混乱を避け、言語の予測可能性を高めることを目的としています。配列自体は値型であり、nil にはなりませんが、ポインタとしての配列や、配列を含む構造体などが nil になり得るため、この制限はコンパイラが配列の「nilness」をチェックする際の挙動を統一しようとしたものと考えられます。これは、Go言語が現在の配列比較のセマンティクス(要素が比較可能な型であれば、要素ごとに比較して等価性をチェックする)に落ち着くまでの過渡期における重要な一歩であったと言えます。

前提知識の解説

  • Go言語の配列 (Arrays in Go): Goの配列は、固定長で同じ型の要素のシーケンスです。配列の型は、要素の型と配列の長さの両方によって定義されます(例: [5]int は5つの整数を持つ配列)。Goの配列は値型であり、代入や関数への引数として渡される際には、その内容がコピーされます。
  • Go言語の比較演算子 (Comparison Operators in Go): Goでは、== (等しい) と != (等しくない) などの比較演算子が提供されています。これらの演算子は、数値型、文字列型、ブール型、チャネル、ポインタ、インターフェース、構造体、配列(特定の条件下)など、さまざまな型に適用されます。
  • nil (Go Language nil): Goにおける nil は、ポインタ、チャネル、関数、インターフェース、マップ、スライスなどのゼロ値を表す事前宣言された識別子です。配列自体は値型であるため、直接 nil にすることはできませんが、ポインタとしての配列や、配列を含む構造体などが nil になり得ます。このコミットでは、配列の比較が nil との等価性/非等価性チェックに限定されている点が重要です。
  • Goコンパイラ (src/cmd/6g/cgen.c): src/cmd/6g/cgen.c は、Goコンパイラのバックエンドの一部であり、Goのソースコードからamd64アーキテクチャ向けのアセンブリコードを生成するコード生成(code generation)を担当しています。bgen 関数は、Goの抽象構文木 (AST) のノードを処理し、対応する機械語命令を生成する役割を担っています。
  • yyerror (Goコンパイラの内部関数): yyerror は、Goコンパイラが構文解析や意味解析の段階でエラーを検出した際に、エラーメッセージを出力するために使用される内部関数です。
  • isdarray (Goコンパイラの内部関数): isdarray は、Goコンパイラの内部で使われる関数で、与えられた型が動的配列(スライス)であるかどうかを判定するために使われる可能性があります。ただし、このコミットの文脈では、darray が固定長配列を指している可能性もあります。Goの初期のコンパイラコードでは、用語が現在のGoの仕様と完全に一致しない場合があります。コミットメッセージが「array compare」と明示していることから、固定長配列の比較に関する変更であると解釈するのが自然です。
  • OEQ, ONE, OLITERAL (Goコンパイラの内部定数): これらはGoコンパイラの内部で使われる演算子を表す定数です。
    • OEQ: 等価演算子 (==)
    • ONE: 非等価演算子 (!=)
    • OLITERAL: リテラル値(定数)

技術的詳細

このコミットは、Goコンパイラのコード生成フェーズ (src/cmd/6g/cgen.c) において、配列の比較に関する新たな制約を導入しています。具体的には、bgen 関数内で、左辺 (nl) が配列型 (isdarray(nl->type)) である場合の比較処理に新しいチェックが追加されました。

追加されたコードは以下の通りです。

			// only valid to cmp darray to literal nil
			if((a != OEQ && a != ONE) || nr->op != OLITERAL) {
				yyerror("illegal array comparison");
				break;
			}

このコードは、配列の比較が以下の条件を満たさない場合に「不正な配列比較 (illegal array comparison)」としてコンパイルエラー (yyerror) を発生させます。

  1. 比較演算子が == (OEQ) または != (ONE) ではない場合: これは、配列に対して <><=>= といった順序比較演算子を使用しようとした場合にエラーとなることを意味します。Goの配列は値型であり、要素の順序は定義されていますが、配列全体としての「大小」を比較する意味は通常ありません。例えば、[1, 2][2, 1] のどちらが大きいかを一意に定義することは困難です。

  2. 右辺 (nr) がリテラルではない場合 (nr->op != OLITERAL): これは、配列の比較の右辺が定数(リテラル)ではない場合にエラーとなることを意味します。

  3. 上記の2つの条件が || (OR) で結合されていること: これは、比較演算子が == または != であり、かつ右辺がリテラルである場合にのみ、配列の比較が許可されることを意味します。しかし、コメント // only valid to cmp darray to literal nil が示すように、このリテラルは nil であることが期待されています。つまり、この変更は、配列の比較を array == nil または array != nil の形式に限定しようとしていることを示唆しています。

現在のGo言語の仕様では、配列は要素の型が比較可能であれば、== および != 演算子で要素ごとに比較されます。例えば、[3]int{1, 2, 3} == [3]int{1, 2, 3}true になります。このコミットは、Go言語が現在の配列比較のセマンティクスに落ち着くまでの過程で、一時的にこのような厳格な制限を設けていたことを示唆しています。特に、nil との比較に限定している点は、配列がポインタのように扱われるべきではないという設計思想の現れかもしれません。

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

src/cmd/6g/cgen.c ファイルの bgen 関数内、if(isdarray(nl->type)) ブロックに以下の5行が追加されました。

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -751,6 +751,11 @@ bgen(Node *n, int true, Prog *to)
 		}

 		if(isdarray(nl->type)) {
+			// only valid to cmp darray to literal nil
+			if((a != OEQ && a != ONE) || nr->op != OLITERAL) {
+				yyerror("illegal array comparison");
+				break;
+			}
 			a = optoas(a, types[tptr]);
 			regalloc(&n1, types[tptr], N);
 			agen(nl, &n1);

コアとなるコードの解説

この変更は、Goコンパイラのコード生成フェーズにおいて、配列の比較に関する新たな制約を導入しています。

  1. if(isdarray(nl->type)): この条件は、現在処理している抽象構文木 (AST) のノード nl の型が配列(または動的配列、スライス)である場合に真となります。このブロック内で配列の比較に関する特別な処理が行われます。

  2. // only valid to cmp darray to literal nil: このコメントは、このコードブロックの意図を明確に示しています。すなわち、「動的配列(または配列)はリテラルな nil とのみ比較可能である」という制約を課すものです。

  3. if((a != OEQ && a != ONE) || nr->op != OLITERAL): これが追加された主要なチェックです。

    • a != OEQ && a != ONE: これは、比較演算子 a が等価 (OEQ、すなわち ==) でも非等価 (ONE、すなわち !=) でもない場合に真となります。つまり、a<> などの順序比較演算子である場合にこの条件が満たされます。
    • nr->op != OLITERAL: これは、比較の右辺 nr がリテラル(定数)ではない場合に真となります。
    • これらの2つの条件が || (OR) で結合されているため、以下のいずれかの条件が満たされると if ブロックが実行されます。
      • 比較演算子が == または != ではない(例: <>)。
      • 比較の右辺がリテラルではない。
  4. yyerror("illegal array comparison");: 上記の if 条件が真となった場合、コンパイラは「illegal array comparison」というエラーメッセージを出力し、コンパイルを中断します。これは、Go言語の設計思想として、配列の比較には特定の制限があることを開発者に強制するものです。

  5. break;: エラーが発生した場合、現在の switch または for ループ(このコードスニペットからは親の構造が不明ですが、bgen 関数の内部ロジックの一部として)から抜け出し、それ以上のコード生成処理を行わないようにします。

この変更は、Go言語の初期段階において、配列の比較セマンティクスを厳密に定義しようとした試みの一環です。特に、配列の順序比較を禁止し、nil とのリテラル比較に限定することで、言語のシンプルさと予測可能性を保とうとしたと考えられます。これは、GoがC言語のようなポインタ演算を許容しつつも、より安全で扱いやすい言語を目指していたことの表れとも言えます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/6g/ ディレクトリ内の関連ファイル)