[インデックス 1573] ファイルの概要
このコミットは、Goプログラミング言語の仕様書(doc/go_spec.txt
)に対する更新です。主な目的は、Go言語における型の比較規則、特にマップのキーとして使用できる型に関する記述を明確にし、いくつかの未解決の問題リストを更新することです。
コミット
commit 7471eab96f3a842cff4b65316c1d8ecf6f19cad5
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jan 27 14:51:24 2009 -0800
- added missing sections on comparisons for some types
- clarified legal map key types (must support comparison)
- updated open issues/todo list
R=r
DELTA=81 (48 added, 19 deleted, 14 changed)
OCL=23580
CL=23621
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7471eab96f3a842cff4b65316c1d8ecf6f19cad5
元コミット内容
このコミットの元々のメッセージは以下の通りです。
- 一部の型に対する比較に関する欠落していたセクションを追加
- 合法的なマップキー型(比較をサポートする必要がある)を明確化
- 未解決の問題/TODOリストを更新
変更の背景
このコミットが行われた2009年1月は、Go言語がまだ初期開発段階にあり、その仕様が活発に策定されていた時期です。Go言語の設計目標の一つに「シンプルさ」と「安全性」があり、型の比較やマップの動作は言語の基本的な振る舞いを定義する上で非常に重要です。
初期の仕様書では、ポインタ型、インターフェース型、マップ型などの複合型における比較のセマンティクスが十分に詳細に記述されていなかった可能性があります。特にマップのキーは、その性質上、一意性を保証するために比較可能である必要があります。しかし、どのような型が比較可能であるか、また比較可能でない型をキーとして使用した場合にどうなるか、といった点が不明瞭だったと考えられます。
また、Go言語の設計者たちは、言語の進化とともに発生する設計上の課題や未決定事項を「Open issues」や「Todo's」として仕様書内にリストアップし、議論を進めていました。このコミットは、そうした未解決の問題リストを最新の状態に保ち、既に解決された項目(例: onreturn/undo
ステートメントが defer
ステートメントになったこと、非基本型の比較に関する議論の決着)を反映させる目的も持っています。
defer
ステートメントの導入は、リソースの解放やクリーンアップ処理を関数がリターンする直前に実行することを保証するGo言語の重要な機能であり、このコミットでその解決が明記されたことは、言語設計の方向性が固まりつつあったことを示唆しています。
前提知識の解説
このコミットを理解するためには、Go言語の基本的な型システム、特に以下の概念についての知識が必要です。
- Go言語の型: Go言語には、基本型(整数、浮動小数点数、真偽値、文字列など)と複合型(配列、スライス、マップ、構造体、インターフェース、ポインタ、チャネルなど)があります。
- 比較演算子 (
==
,!=
): Go言語では、多くの型に対して等価性(==
)と不等価性(!=
)の比較が可能です。しかし、すべての型が比較可能であるわけではありません。特に、スライス、マップ、関数は直接比較できません(nil
との比較を除く)。 - ポインタ型: メモリ上のアドレスを指す型です。Go言語ではポインタ演算は制限されていますが、ポインタの等価性比較は可能です。
- インターフェース型: 異なる具象型が共通の振る舞いを共有するための仕組みです。インターフェース型の値は、内部的に具象型と値を保持します。インターフェースの比較は、その動的な型と値の両方が等しい場合にのみ真となります。
- マップ型: キーと値のペアを格納するハッシュテーブルです。マップのキーは比較可能でなければなりません。これは、マップがキーのハッシュ値を計算し、キーの等価性を比較して要素を検索・挿入・削除するためです。
defer
ステートメント: Go言語のユニークな機能で、関数がリターンする直前に実行される関数呼び出しをスケジュールします。これにより、リソースのクリーンアップなどを確実に行うことができます。- Go言語の仕様書: Go言語の公式な定義であり、言語の構文、セマンティクス、組み込み関数などが詳細に記述されています。初期の仕様書は現在とは異なる形式(
doc/go_spec.txt
)で管理されていました。
技術的詳細
このコミットは、Go言語の仕様書における以下の重要な点を明確にしています。
-
ポインタ型の比較:
- ポインタ型は
nil
と比較可能です。 - 同じポインタ型の2つの変数は、
==
および!=
演算子で比較可能です。これらは、両方のポインタが同じメモリ位置を指している場合に等しいと判断されます。 - ポインタ演算は許可されていません。
- ポインタ型は
-
インターフェース型の比較:
- インターフェース型の2つの変数は、両方の変数が同じ静的型を持つ場合に
==
および!=
演算子で比較可能です。 - 比較は、両者の動的な型と値が等しい場合に真となります。
- 重要な変更点: 動的な型が等しいが、その値が比較をサポートしない場合(例: 動的な型がスライスやマップである場合)、実行時エラーが発生することが明記されました。これは、インターフェースの比較に関するセマンティクスを厳密に定義し、予期せぬ動作を防ぐための重要な追加です。
- インターフェース型の2つの変数は、両方の変数が同じ静的型を持つ場合に
-
マップのキー型:
- マップのキー型には、
==
および!=
演算子が定義されている必要があります。 - したがって、キー型は基本型、ポインタ型、インターフェース型、またはチャネル型でなければなりません。
- キー型がインターフェース型の場合、動的なキー型がこれらの比較演算子をサポートしている必要があります。
- 比較をサポートしないキーを持つ値をマップに挿入しようとすると、実行時エラーが発生することが明記されました。これにより、マップのキーとして使用できる型の制約が明確になり、マップの整合性が保証されます。
- マップのキー型には、
-
比較演算子の適用範囲の明確化:
- 以前の記述では、
==
および!=
演算子が「スライス、マップ、チャネル型」にも適用されるとされていましたが、これは誤解を招く可能性がありました。 - 新しい記述では、スライスとマップ型は、事前宣言された値
nil
との等価性テストのみをサポートすることが明確にされました。これにより、スライスやマップの直接比較ができないというGo言語の特性が正しく反映されました。
- 以前の記述では、
-
defer
ステートメントの明確化:defer
ステートメントの式は、関数呼び出しだけでなく、メソッド呼び出しも可能であることが追記されました。- また、
defer
の例でprint(i)
がfmt.Print(i)
に変更されています。これは、Go言語の標準ライブラリの整備が進み、より適切な関数が利用可能になったことを示唆しています。
-
Open Issues / Todo List の更新:
onreturn/undo
ステートメントがdefer
ステートメントとして解決されたことが明記されました。- 非基本型の比較、特にマップのキーに関する議論が解決済みとしてマークされました。
- コンバージョン(型変換)に関する問題、型宣言のセマンティクス、チャネル変換の必要性など、いくつかの主要な未解決の問題が引き続きリストアップされています。これは、Go言語の設計が継続的に進化している過程を示しています。
これらの変更は、Go言語の型システムとランタイムの堅牢性を高め、開発者がより予測可能で安全なコードを書けるようにするための重要なステップでした。特に、比較可能性のルールを厳密に定義することで、マップのようなデータ構造の動作が明確になり、実行時エラーのリスクが低減されます。
コアとなるコードの変更箇所
このコミットによる主要な変更は、doc/go_spec.txt
ファイル内の以下のセクションに集中しています。
-
Open Issues / Todo List の更新:
<!--
と-->
で囲まれたコメントブロック内の「Biggest open issues」、「Decisions in need of integration into the doc」、「Missing」、「Wish list」、「Todo's」、「Open issues」、「Closed」セクションが大幅に更新されています。- 特に「Closed」セクションに
[x] onreturn/undo statement - now: defer statement
と[x] comparison of non-basic types: what do we allow? ...
が追加されています。
-
ポインタ型の比較に関する記述の追加:
Pointer types
セクションに以下の記述が追加されました。@@ -1305,6 +1309,16 @@ such as: Assignment compatibility: A pointer is assignment compatible to a variable of pointer type, only if both types are equal. +Comparisons: A variable of pointer type can be compared against "nil" with the +operators "==" and "!=" (§Comparison operators). The variable is +"nil" only if "nil" is assigned explicitly to the variable (§Assignments), or +if the variable has not been modified since creation (§Program initialization +and execution). + +Two variables of equal pointer type can be tested for equality with the +operators "==" and "!=" (§Comparison operators). The pointers are equal +if they point to the same location. + Pointer arithmetic of any kind is not permitted.
-
インターフェース型の比較に関する記述の修正:
Interface types
セクションのTODO
コメントが削除され、実行時エラーに関する記述が追加されました。@@ -1421,10 +1435,8 @@ and execution). Two variables of interface type can be tested for equality with the operators "==" and "!=" (§Comparison operators) if both variables have the same static type. They are equal if both their dynamic types and values are -equal. - -TODO: Document situation where the dynamic types are equal but the values -don't support comparison. +equal. If the dynamic types are equal but the values do not support comparison, +a run-time error occurs.
-
マップのキー型に関する記述の追加と修正:
Map types
セクションに、キー型が比較可能でなければならないという制約が追加されました。@@ -1517,22 +1529,29 @@ negative. KeyType = CompleteType . ValueType = CompleteType . +The comparison operators "==" and "!=" (§Comparison operators) must be defined +for operands of the key type; thus the key type must be a basic, pointer,\n+interface, or channel type. If the key type is an interface type,\n+the dynamic key types must support these comparison operators. In this case,\n+inserting a map value with a key that does not support testing for equality\n+is a run-time error.\n+\n Upon creation, a map is empty and values may be added and removed during execution. map [string] int - map [struct { pid int; name string }] chan Buffer + map [*T] struct { x, y float } map [string] interface {} The length of a map "m" can be discovered using the built-in function len(m) -The value of an uninitialized map is "nil". A new, empty map -value for given key and value types K and V is made using the built-in -function "make" which takes the map type and an (optional) capacity as arguments: +The value of an uninitialized map is "nil". A new, empty map value for given +map type M is made using the built-in function "make" which takes the map type +and an optional capacity as arguments: - my_map := make(map[K] V, 100); + my_map := make(M, 100); The map capacity is an allocation hint for more efficient incremental growth of the map.
-
比較演算子の適用範囲の修正:
Comparison operators
セクションで、スライスとマップがnil
との比較のみをサポートすることが明確化されました。@@ -2300,8 +2319,9 @@ Comparison operators Comparison operators yield a boolean result. All comparison operators apply to strings and numeric types. The operators "==" and "!=" also apply to -boolean values, pointer, interface, slice, map, and channel types -(including the value "nil"). +boolean values, pointer, interface, and channel types. Slice and +map types only support testing for equality against the predeclared value +"nil".
-
go
ステートメントとdefer
ステートメントの記述修正:GoStat
とDeferStat
の説明で、式が「関数呼び出し」だけでなく「メソッド呼び出し」も可能であることが追記されました。defer
の例がdefer print(i);
からdefer fmt.Print(i);
に変更されました。@@ -2822,7 +2842,7 @@ Go statements A go statement starts the execution of a function as an independent concurrent thread of control within the same address space. The expression -must evaluate into a function call. +must be a function or method call. GoStat = "go" Expression . @@ -3025,8 +3045,8 @@ when the surrounding function returns. DeferStat = "defer" Expression . -The expression must be a function call. Each time the defer statement executes,\n-the parameters to the function call are evaluated and saved anew but the +The expression must be a function or method call. Each time the defer statement\n+executes, the parameters to the function call are evaluated and saved anew but the function is not invoked. Immediately before the innermost function surrounding the defer statement returns, but after its return value (if any) is evaluated,\n each deferred function is executed with its saved parameters. Deferred functions @@ -3037,7 +3057,7 @@ are executed in LIFO order. // prints 3 2 1 0 before surrounding function returns for i := 0; i <= 3; i++ { - defer print(i); + defer fmt.Print(i); }
コアとなるコードの解説
このコミットの核心は、Go言語の型システムにおける「比較可能性」のルールを厳密に定義し、特にマップのキーとしての型の適格性を明確にした点にあります。
マップのキー型の制約
Go言語のマップはハッシュテーブルとして実装されており、キーのハッシュ値を計算し、キー同士を比較することで要素の検索、挿入、削除を行います。このため、マップのキーとして使用される型は、必ず等価性(==
)と不等価性(!=
)の比較をサポートしている必要があります。
このコミットでは、以下の型がマップのキーとして適格であると明記されました。
- 基本型: 整数型、浮動小数点数型、真偽値型、文字列型。これらはすべて比較可能です。
- ポインタ型: 同じメモリ位置を指すかどうかで比較可能です。
- インターフェース型: 動的な型と値が比較可能であればキーとして使用できます。ただし、動的な型が比較をサポートしない場合(例: スライスやマップ)、実行時エラーが発生します。
- チャネル型: 同じチャネルを指すかどうかで比較可能です。
逆に、スライス、マップ、関数型は直接比較できないため、マップのキーとして使用することはできません。この制約は、マップの内部的な整合性を保ち、予測可能な動作を保証するために不可欠です。
インターフェース型の比較の厳密化
インターフェース型の比較に関する記述の変更も重要です。以前は「動的な型が等しいが値が比較をサポートしない状況」についてのTODOコメントがありましたが、このコミットで「実行時エラーが発生する」と明確にされました。これは、Go言語が型安全性を重視し、曖昧な動作を許容しない設計哲学を反映しています。例えば、interface{}
型の変数にスライスが格納されている場合、そのインターフェース変数を別のインターフェース変数と比較しようとすると、実行時エラーになります。
defer
ステートメントの拡張
defer
ステートメントの式が関数呼び出しだけでなくメソッド呼び出しも可能になったことは、Go言語の表現力を高める小さな改善です。これにより、オブジェクト指向的なクリーンアップ処理をより自然に記述できるようになります。また、print
から fmt.Print
への変更は、Go言語の標準ライブラリが成熟し、より構造化されたI/O操作が推奨されるようになったことを示しています。
これらの変更は、Go言語が初期段階で直面していた設計上の課題を解決し、言語仕様をより堅牢で明確なものにするための継続的な努力の一環です。特に、型の比較可能性に関する厳密なルールは、Go言語のシンプルさと安全性を維持する上で重要な基盤となっています。
関連リンク
- Go言語の公式ウェブサイト: https://golang.org/
- Go言語の仕様書(現在のバージョン): https://go.dev/ref/spec
- Go言語の初期のコミット履歴(GitHub): https://github.com/golang/go/commits/master?after=7471eab96f3a842cff4b65316c1d8ecf6f19cad5+34&branch=master
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のGitHubリポジトリのコミット履歴
- Go言語の設計に関する初期の議論やメーリングリストのアーカイブ(一般公開されている場合)
- Go言語の型システムに関する一般的な解説記事
- Go言語のマップの内部実装に関する技術記事
- Go言語の
defer
ステートメントに関する解説記事