[インデックス 14670] ファイルの概要
このコミットは、Go言語のコンパイラにおけるマップのキーに関する挙動、特に構造体(struct)をマップのキーとして使用し、そのキーがコンパイル時に計算可能であっても定数ではない場合に、重複するキーがコンパイルエラーとならないという特定のケースをテストするために追加されたものです。これは、Go言語の仕様とコンパイラの挙動の間の微妙な差異を浮き彫りにするものであり、issue 4555
に関連しています。
コミット
commit 94430937ac9330a4c7e8ed23659bc4475f520739
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 17 11:05:58 2012 -0500
test: add "duplicate" struct map key test
Update #4555.
R=gri, iant
CC=golang-dev
https://golang.org/cl/6944059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/94430937ac9330a4c7e8ed23659bc4475f520739
元コミット内容
test: add "duplicate" struct map key test
Update #4555.
R=gri, iant
CC=golang-dev
https://golang.org/cl/6944059
変更の背景
このコミットは、Go言語のコンパイラがマップリテラル(map literal)内で重複するキーをどのように扱うかという、特定のコーナーケースに対処するために追加されました。Go言語の仕様では、マップリテラル内で重複するキーが存在する場合、コンパイルエラーとなるべきです。しかし、このルールは「定数」であるキーに適用されます。
issue 4555
は、構造体(struct)をマップのキーとして使用した場合に、その構造体の値がコンパイル時に計算可能であっても、Go言語の仕様における「定数」の定義に合致しないために、重複するキーがコンパイルエラーとして検出されないという問題点を指摘していました。
具体的には、Key{1,2}
のような構造体リテラルは、その値がコンパイル時に決定されるにもかかわらず、Go言語の仕様上は「定数式」とはみなされません。そのため、map[Key]string{Key{1,2}: "hello", Key{1,2}: "world"}
のように同じ構造体リテラルをキーとして複数回記述しても、コンパイラはこれを重複キーエラーとして報告しませんでした。このコミットは、この挙動を明示的にテストケースとして追加し、将来的なコンパイラの修正や仕様の明確化に備えるものです。
前提知識の解説
Go言語のマップ(map)
Go言語のマップは、キーと値のペアを格納するハッシュテーブルの実装です。マップのキーは比較可能(comparable)な型である必要があります。これには、数値型、文字列型、ポインタ型、チャネル型、インターフェース型、そして比較可能なフィールドのみを持つ構造体型が含まれます。
マップリテラル(map literal)
マップリテラルは、マップの初期値を宣言するための構文です。例えば、map[string]int{"one": 1, "two": 2}
のように記述します。
定数(Constants)と定数式(Constant expressions)
Go言語において、定数はコンパイル時にその値が決定される不変の値を指します。定数式は、定数のみで構成され、コンパイル時に評価される式です。Go言語の仕様では、マップリテラル内で重複するキーが定数式である場合にコンパイルエラーを発生させると規定されています。
構造体(Structs)
構造体は、異なる型のフィールドをまとめた複合データ型です。構造体の値は、そのすべてのフィールドの値が等しい場合に等しいとみなされます。構造体は、そのすべてのフィールドが比較可能であれば、マップのキーとして使用できます。
issue 4555
Go言語の公式Issueトラッカーで報告された問題です。このIssueは、構造体リテラルがコンパイル時に計算可能であっても、Go言語の仕様における「定数」の定義に合致しないため、マップリテラル内で重複する構造体キーがコンパイルエラーとして検出されないという挙動について議論しています。
技術的詳細
Go言語のコンパイラは、マップリテラル内のキーが重複しているかどうかをチェックします。このチェックは、キーが「定数式」である場合にのみ、コンパイルエラーとして報告されます。
このコミットで追加されたテストケースは、以下のコードスニペットを含んでいます。
type Key struct {X, Y int}
var _ = map[Key]string{
Key{1,2}: "hello",
Key{1,2}: "world",
}
ここで、Key{1,2}
は構造体リテラルです。このリテラルの値はコンパイル時に決定されますが、Go言語の仕様における「定数式」の厳密な定義には合致しません。定数式は、数値、真偽値、文字列の定数、またはそれらから構成される式に限られます。構造体リテラルは、たとえそのフィールドがすべて定数であっても、それ自体は定数式とはみなされません。
そのため、コンパイラは Key{1,2}
が重複していることを検出しても、それが定数式ではないため、コンパイルエラーを発生させません。この場合、マップの初期化時には、後から記述された Key{1,2}: "world"
が Key{1,2}: "hello"
を上書きし、最終的にマップは Key{1,2}: "world"
というエントリを持つことになります。
このテストの目的は、この特定の挙動(重複する非定数構造体キーがエラーにならないこと)を文書化し、コンパイラのテストスイートに含めることで、将来的にこの挙動が意図せず変更されることを防ぐことにあります。また、これはGo言語の仕様と実装の間の微妙なニュアンスを理解する上で重要なテストケースとなります。
コアとなるコードの変更箇所
変更は test/initializerr.go
ファイルに対して行われました。
--- a/test/initializerr.go
+++ b/test/initializerr.go
@@ -26,3 +26,15 @@ var a5 = []byte { x: 2 }\t// ERROR "index"
var ok1 = S { }\t// should be ok
var ok2 = T { S: ok1 }\t// should be ok
++
+// These keys can be computed at compile time but they are
+// not constants as defined by the spec, so they do not trigger
+// compile-time errors about duplicate key values.
+// See issue 4555.
++
+type Key struct {X, Y int}
++
+var _ = map[Key]string{
++ Key{1,2}: "hello",
++ Key{1,2}: "world",
++}
コアとなるコードの解説
追加されたコードは、test/initializerr.go
の既存の初期化子関連のテストの最後に追加されています。
// These keys can be computed at compile time but they are
// not constants as defined by the spec, so they do not trigger
// compile-time errors about duplicate key values.
// See issue 4555.
type Key struct {X, Y int}
var _ = map[Key]string{
Key{1,2}: "hello",
Key{1,2}: "world",
}
// These keys can be computed at compile time but they are ...
から始まるコメントは、このテストケースの意図を明確に説明しています。すなわち、Key{1,2}
のような構造体リテラルはコンパイル時にその値が決定される(計算可能である)にもかかわらず、Go言語の仕様における「定数」の定義には合致しないため、重複キーエラーが発生しないことを示しています。type Key struct {X, Y int}
:X
とY
という2つの整数フィールドを持つKey
という名前の構造体を定義しています。構造体は、そのすべてのフィールドが比較可能であれば、マップのキーとして使用できます。int
は比較可能な型なので、Key
型もマップのキーとして有効です。var _ = map[Key]string{ ... }
: これはマップリテラルを使用してマップを初期化しています。_
(ブランク識別子) に代入することで、このマップ自体は使用しないが、その初期化処理(コンパイル時のチェックを含む)を実行することを意図しています。Key{1,2}: "hello", Key{1,2}: "world",
: ここがテストの核心部分です。同じKey{1,2}
という構造体リテラルが2回キーとして使用されています。前述の通り、Key{1,2}
はコンパイル時に計算可能ですが、定数式ではないため、この重複はコンパイルエラーとはなりません。このテストがコンパイルエラーなしで通過することによって、この特定の挙動がGoコンパイラの意図された動作であることが確認されます。
このテストケースは、Go言語のコンパイラがマップリテラル内の重複キーをどのように扱うかという、仕様の微妙な側面を浮き彫りにし、その挙動を保証するための重要な追加です。
関連リンク
- Go言語のマップに関する公式ドキュメント: https://go.dev/blog/maps
- Go言語の仕様(マップリテラルに関するセクション): https://go.dev/ref/spec#Map_literals
- Go言語の仕様(定数に関するセクション): https://go.dev/ref/spec#Constants
参考にした情報源リンク
- Go Issue 4555:
cmd/gc: map literal duplicate key check too strict
(または類似のタイトルで検索) - このコミットが参照しているGoの公式Issueトラッカーのエントリ。 - Gerrit Code Review for Go (CL 6944059): https://go.dev/cl/6944059
- このコミットに対応するGerritの変更リスト。詳細な議論やレビューコメントが含まれている場合があります。
- Go言語の公式ブログやドキュメント。
- Go言語のソースコードリポジトリ。