[インデックス 10624] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)と標準ライブラリのreflect
パッケージにおける変更を扱っています。具体的には、map
型とfunc
型がインターフェースを介して比較された場合に、等価性チェックを許可しないようにする修正です。これは、以前のコミットで直接的なmap
やfunc
の等価性比較が削除された際に、インターフェースを介した比較が見落とされていたことに対する対応です。
コミット
commit 46deaa297bcf76438ae17b070ef351d5b91d3d59
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 6 10:48:17 2011 -0500
gc: disallow map/func equality via interface comparison
Missed when I removed direct map/func equality.
R=ken2
CC=golang-dev
https://golang.org/cl/5452052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/46deaa297bcf76438ae17b070ef351d5b91d3d59
元コミット内容
gc: disallow map/func equality via interface comparison
(gc: インターフェース比較によるmap/funcの等価性チェックを不許可にする)
Missed when I removed direct map/func equality.
(直接的なmap/funcの等価性チェックを削除した際に見落としていた。)
変更の背景
Go言語では、map
型とfunc
型は、その性質上、直接的な等価性比較が許可されていません。
map
型:map
は参照型であり、その等価性を定義することは複雑です。内容が同じであれば等しいとみなすのか、それともメモリ上のアドレスが同じであれば等しいとみなすのか、といった問題があります。Goの設計思想では、このような曖昧さを避けるため、map
の直接比較は許可されていません。map
の等価性を確認したい場合は、要素を一つずつ比較するなどの明示的なロジックを記述する必要があります。func
型:func
も参照型であり、その等価性比較は通常、メモリ上のアドレス比較を意味します。しかし、クロージャや異なるパッケージで定義された関数など、アドレスが異なっても論理的に同じ関数とみなすべきケースが存在し、プログラマが意図する「等価性」とシステムが提供する「アドレス等価性」が乖離する可能性があります。そのため、Goではfunc
の直接比較も許可されていません。
このコミット以前に、Goコンパイラはmap
やfunc
の直接的な等価性比較を既に禁止していました。しかし、これらの型がinterface{}
(空インターフェース)にラップされた場合、インターフェースの等価性比較ルール(動的な型と値が等しい場合に等しい)が適用され、結果的にmap
やfunc
の比較が意図せず行われてしまう可能性がありました。
このコミットは、この「見落とし」を修正し、map
やfunc
がインターフェースを介して比較された場合でも、ランタイムパニックを引き起こすことで、その比較を明示的に禁止することを目的としています。これにより、Go言語の型システムにおけるmap
とfunc
の比較に関する一貫性が保たれます。
前提知識の解説
Go言語の型システムと等価性
Go言語では、型によって等価性比較のルールが異なります。
- 比較可能な型:
- 数値型、文字列型、真偽値型は直接比較可能です。
- ポインタ型は、同じポインタを指している場合に等しいとみなされます。
- チャネル型は、同じチャネル値を指している場合に等しいとみなされます(チャネルの同一性)。
- 構造体型は、そのすべてのフィールドが比較可能であり、かつすべてのフィールドが等しい場合に比較可能です。
- 配列型は、その要素型が比較可能であり、かつすべての要素が等しい場合に比較可能です。
- インターフェース型は、その動的な型と動的な値が両方とも等しい場合に比較可能です。動的な値の比較は、その基底の型が比較可能である場合にのみ行われます。
- 比較不可能な型:
map
型: 前述の通り、その性質上、直接比較はできません。func
型: 前述の通り、その性質上、直接比較はできません。- スライス型: スライスは内部的にポインタ、長さ、容量を持つ構造体ですが、その要素は直接比較されません。スライスの等価性を確認するには、要素を一つずつ比較する必要があります。
Goコンパイラ (gc
)
gc
はGo言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。型チェック、最適化、コード生成など、コンパイルプロセスの様々な段階でGo言語の仕様に準拠しているかを確認します。このコミットでは、src/cmd/gc/subr.c
というファイルが変更されており、これはコンパイラのサブルーチン、特に型に関する処理の一部を担っていると考えられます。
reflect
パッケージ
reflect
パッケージは、Goプログラムの実行時に、その構造(型、値、メソッドなど)を検査・操作するための機能を提供します。リフレクションは、型がコンパイル時に不明な場合や、汎用的なデータ処理を行う場合に有用です。このコミットでは、src/pkg/reflect/all_test.go
が変更されており、reflect
パッケージがfunc
型の値を扱う際のテストが修正されています。
インターフェースの等価性比較
Goのインターフェースは、動的な型と動的な値の2つの要素で構成されます。インターフェース値が等しいとみなされるのは、以下の両方の条件を満たす場合です。
- 動的な型が同じである。
- 動的な値が等しい(基底の型が比較可能である場合)。
このコミットの背景にある問題は、map
やfunc
といった比較不可能な型がインターフェースに格納された場合、インターフェースの等価性比較ルールが適用され、結果的に比較不可能な型が比較されてしまうという点でした。Goのランタイムは、このような比較が行われた際にパニックを引き起こすことで、不正な操作を防止します。
技術的詳細
このコミットの技術的な核心は、Goコンパイラがmap
型とfunc
型をインターフェースを介した比較から除外するように変更された点にあります。
-
コンパイラレベルでの変更 (
src/cmd/gc/subr.c
):algtype
関数は、Goコンパイラ内で型の「アルゴリズムタイプ」を決定するために使用されると考えられます。これは、特定の型に対してどのような操作(例えば、等価性比較、メモリコピーなど)が許可されるかを決定する内部的な分類です。 変更前は、map
(TMAP
) とfunc
(TFUNC
) 型が、issimple
(単純な型)やisptr
(ポインタ型)などと同様に、特定の比較可能なカテゴリに含まれていました。このコミットでは、t->etype == TFUNC || t->etype == TMAP
という条件が削除され、map
とfunc
がこの比較可能なカテゴリから明示的に除外されました。 これにより、コンパイラはこれらの型がインターフェースを介して比較されることを許可せず、ランタイムでパニックを引き起こすようなコードを生成するようになります。 -
reflect
パッケージのテスト修正 (src/pkg/reflect/all_test.go
):TestFunctionValue
というテスト関数は、func()
型の値をinterface{}
に格納し、その値をreflect.ValueOf
でラップしてテストしていました。 変更前は、v.Interface() != v.Interface() || v.Interface() != x
という直接的な等価性比較が行われていました。これは、func
型がインターフェースを介して比較可能であるという誤った前提に基づいています。 このコミットでは、比較方法がfmt.Sprint(v.Interface()) != fmt.Sprint(x)
に変更されました。fmt.Sprint
は値の文字列表現を生成するため、これは等価性比較ではなく、値の文字列表現が同じであるかどうかのチェックになります。これにより、func
型の直接比較が禁止されたGoのセマンティクスにテストが適合するようになりました。 -
新しいテストケースの追加 (
test/interface/noeq.go
): この新しいテストファイルは、map
型、struct{}
型(フィールドがないため比較可能)、func
型がインターフェースに格納された場合に、等価性比較がどのように振る舞うかを検証します。cmp(x interface{}) bool { return x == x }
という関数は、インターフェース値の等価性比較を行います。noCmp(x interface{})
関数は、cmp(x)
を呼び出し、その呼び出しがパニックを引き起こすことをshouldPanic
関数で検証します。 このテストは、map
とfunc
がインターフェースを介して比較された場合に、期待通りにパニックが発生することを確認します。struct{}
は比較可能なので、noCmp(s)
はパニックを引き起こしません(テストコードではnoCmp(s)
もパニックを期待しているように見えますが、これはstruct{}
が比較可能であるというGoのルールと矛盾します。ただし、このコミットの主眼はmap
とfunc
にあります)。
これらの変更により、Go言語はmap
とfunc
の等価性比較に関する一貫したルールを適用し、インターフェースを介した比較も禁止することで、予期せぬ動作やバグを防ぎます。
コアとなるコードの変更箇所
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -499,8 +499,7 @@ algtype(Type *t)
{
int a;
- if(issimple[t->etype] || isptr[t->etype] ||
- t->etype == TCHAN || t->etype == TFUNC || t->etype == TMAP) {
+ if(issimple[t->etype] || isptr[t->etype] || t->etype == TCHAN) {
if(t->width == 1)
a = AMEM8;
else if(t->width == 2)
src/pkg/reflect/all_test.go
--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -468,8 +468,8 @@ func TestInterfaceValue(t *testing.T) {
func TestFunctionValue(t *testing.T) {
var x interface{} = func() {}
v := ValueOf(x)
- if v.Interface() != v.Interface() || v.Interface() != x {
- t.Fatalf("TestFunction != itself")
+ if fmt.Sprint(v.Interface()) != fmt.Sprint(x) {
+ t.Fatalf("TestFunction returned wrong pointer")
}
assert(t, v.Type().String(), "func()")
}
test/interface/noeq.go
(新規ファイル)
// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: interface/noeq
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Interface values containing types that cannot be compared for equality.
package main
func main() {
cmp(1)
var (
m map[int]int
s struct{}
f func()
)
noCmp(m)
noCmp(s)
noCmp(f)
}
func cmp(x interface{}) bool {
return x == x
}
func noCmp(x interface{}) {
shouldPanic(func() { cmp(x) })
}
func shouldPanic(f func()) {
defer func() {
if recover() == nil {
panic("function should panic")
}
}()
f()
}
コアとなるコードの解説
src/cmd/gc/subr.c
の変更
algtype
関数は、Goコンパイラのバックエンドで、特定の型に対してどのような「アルゴリズム」を適用すべきかを決定する役割を担っています。ここでいう「アルゴリズム」とは、メモリのコピー、ゼロクリア、等価性比較などの低レベルな操作を指します。
変更前のコードでは、if(issimple[t->etype] || isptr[t->etype] || t->etype == TCHAN || t->etype == TFUNC || t->etype == TMAP)
という条件がありました。これは、issimple
(プリミティブ型など)、isptr
(ポインタ型)、TCHAN
(チャネル型)、TFUNC
(関数型)、TMAP
(マップ型)のいずれかに該当する場合、特定のメモリ操作アルゴリズム(例えば、バイト単位の比較やコピー)が適用されることを示唆しています。
このコミットでは、t->etype == TFUNC || t->etype == TMAP
の部分が削除されました。これにより、func
型とmap
型は、この条件に合致しなくなり、結果としてコンパイラはこれらの型がインターフェースを介して比較された場合に、ランタイムパニックを引き起こすようなコードを生成するようになります。これは、これらの型が「単純な」バイト比較やポインタ比較では等価性を正しく判断できないため、コンパイラがその比較を許可しないようにするための重要な変更です。
src/pkg/reflect/all_test.go
の変更
TestFunctionValue
は、reflect
パッケージが関数値を正しく扱えるかをテストするものです。
変更前は、v.Interface() != v.Interface() || v.Interface() != x
という行で、func()
型の値が格納されたインターフェースの直接比較を行っていました。Go言語のセマンティクスでは関数は比較不可能であるため、このテストはGoの設計意図と矛盾していました。
変更後、この行はif fmt.Sprint(v.Interface()) != fmt.Sprint(x)
に修正されました。fmt.Sprint
は、Goの値を人間が読める文字列形式に変換する関数です。関数値の場合、通常はそのメモリ上のアドレスや内部表現を示す文字列が生成されます。この変更により、テストは関数の「等価性」を直接比較するのではなく、その文字列表現が同じであるかを確認するようになりました。これは、関数が比較不可能であるというGoのルールに準拠しつつ、テストの目的(reflect
パッケージが関数値を正しく取得できるか)を達成するための適切な修正です。
test/interface/noeq.go
の新規追加
このファイルは、map
型とfunc
型がインターフェースを介して比較された場合に、ランタイムパニックが発生することを検証するための新しい統合テストです。
cmp(x interface{}) bool { return x == x }
: この関数は、任意のインターフェース値x
を受け取り、x == x
という比較を行います。Goのインターフェースの等価性ルールに従い、基底の型が比較可能であれば値の比較が行われます。noCmp(x interface{})
: この関数は、cmp(x)
を呼び出し、その呼び出しがパニックを引き起こすことを期待します。shouldPanic(f func())
: このヘルパー関数は、引数として渡された関数f
を実行し、それがパニックを引き起こすことを検証します。もしパニックが発生しなければ、それ自体がパニックを引き起こします。
main
関数では、map[int]int
型の変数m
とfunc()
型の変数f
を宣言し、それぞれをnoCmp
関数に渡しています。これにより、map
とfunc
がインターフェースに格納された状態で等価性比較が行われると、ランタイムパニックが発生することがテストされます。
この新しいテストは、コンパイラとランタイムの変更が期待通りに機能し、map
とfunc
のインターフェースを介した比較が正しく禁止されていることを保証します。
関連リンク
- Go言語の仕様: https://go.dev/ref/spec (特に "Comparison operators" のセクション)
- Go言語の
reflect
パッケージ: https://pkg.go.dev/reflect - Go言語の
fmt
パッケージ: https://pkg.go.dev/fmt
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- Go言語のIssueトラッカーやメーリングリスト(
golang-dev
)の議論 - Go言語における型の比較に関するブログ記事やチュートリアル
[インデックス 10624] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)と標準ライブラリのreflect
パッケージにおける重要な修正を導入しています。主な目的は、map
型とfunc
型がインターフェースを介して比較された場合に、その等価性チェックを明示的に禁止することです。これは、以前のコミットでmap
やfunc
の直接的な等価性比較が削除された際に、インターフェースを介した比較という「抜け穴」が見落とされていたことに対する対応であり、Go言語の型システムにおける比較可能性のルールの一貫性を強化します。
コミット
commit 46deaa297bcf76438ae17b070ef351d5b91d3d59
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 6 10:48:17 2011 -0500
gc: disallow map/func equality via interface comparison
Missed when I removed direct map/func equality.
R=ken2
CC=golang-dev
https://golang.org/cl/5452052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/46deaa297bcf76438ae17b070ef351d5b91d3d59
元コミット内容
gc: disallow map/func equality via interface comparison
(gc: インターフェース比較によるmap/funcの等価性チェックを不許可にする)
Missed when I removed direct map/func equality.
(直接的なmap/funcの等価性チェックを削除した際に見落としていた。)
変更の背景
Go言語の設計において、map
型とfunc
型は、その性質上、直接的な等価性比較が許可されていません。
map
型:map
は参照型であり、その等価性を定義することは複雑です。例えば、2つのマップが「等しい」とは、メモリ上の同じインスタンスを指すことなのか、それとも内容が完全に一致することなのか、という曖昧さがあります。Goはこのような曖昧さを避けるため、map
の直接比較を禁止しています。マップの内容を比較したい場合は、プログラマが明示的に要素を一つずつ比較するロジックを記述する必要があります。func
型:func
も参照型であり、その等価性比較は通常、メモリ上のアドレス比較を意味します。しかし、クロージャ(外部の変数をキャプチャする関数)や異なるパッケージで定義された関数など、アドレスが異なっても論理的に同じ関数とみなすべきケースが存在し、プログラマの意図とシステムが提供する比較結果が乖離する可能性があります。このため、Goではfunc
の直接比較も禁止されています。
このコミット以前、Goコンパイラはmap
やfunc
の直接的な等価性比較を既に禁止していました。しかし、これらの比較不可能な型がinterface{}
(空インターフェース)にラップされた場合、インターフェースの等価性比較ルール(動的な型と値が等しい場合に等しい)が適用され、結果的にmap
やfunc
の比較が意図せず行われてしまうという「抜け穴」が存在していました。Goのランタイムは、比較不可能な型がインターフェースを介して比較された場合、ランタイムパニックを引き起こすことで不正な操作を防止します。
このコミットは、この「見落とし」を修正し、map
やfunc
がインターフェースを介して比較された場合でも、コンパイラがその比較を許可しないように変更することで、ランタイムパニックを確実に発生させるようにします。これにより、Go言語の型システムにおけるmap
とfunc
の比較に関する一貫性が保たれ、開発者が予期せぬ動作に遭遇するのを防ぎます。
前提知識の解説
Go言語の型システムと等価性
Go言語では、型によって等価性比較のルールが厳密に定められています。
-
比較可能な型:
- 数値型、文字列型、真偽値型: これらは直接比較可能です。
- ポインタ型: 同じメモリ上のアドレスを指している場合に等しいとみなされます。
- チャネル型: 同じチャネル値を指している場合に等しいとみなされます(チャネルの同一性)。
- 構造体型: そのすべてのフィールドが比較可能であり、かつすべてのフィールドが等しい場合に比較可能です。
- 配列型: その要素型が比較可能であり、かつすべての要素が等しい場合に比較可能です。
- インターフェース型: その動的な型と動的な値が両方とも等しい場合に比較可能です。動的な値の比較は、その基底の型が比較可能である場合にのみ行われます。
-
比較不可能な型:
map
型: 前述の通り、直接比較はできません。nil
マップとの比較、または両方がnil
であるかどうかの比較は可能です。func
型: 前述の通り、直接比較はできません。nil
関数との比較は可能です。- スライス型: スライスは内部的にポインタ、長さ、容量を持つ構造体ですが、その要素は直接比較されません。スライスの等価性を確認するには、要素を一つずつ比較する必要があります。
Go 1.18で導入されたcomparable
インターフェースは、比較可能なすべての非インターフェース型を表す事前宣言されたインターフェース型です。これは型制約としてのみ使用でき、変数の型としては使用できません。
Goコンパイラ (gc
)
gc
はGo言語の公式コンパイラであり、Goのソースコードを機械語に変換する役割を担っています。型チェック、最適化、コード生成など、コンパイルプロセスの様々な段階でGo言語の仕様に準拠しているかを確認します。このコミットでは、src/cmd/gc/subr.c
というファイルが変更されており、これはコンパイラのサブルーチン、特に型に関する処理の一部を担っていると考えられます。algtype
関数は、Goコンパイラ内で型の「アルゴリズムタイプ」を決定するために使用され、特定の型に対してどのような操作(例えば、等価性比較、メモリコピーなど)が許可されるかを決定します。
reflect
パッケージ
reflect
パッケージは、Goプログラムの実行時に、その構造(型、値、メソッドなど)を検査・操作するための機能を提供します。リフレクションは、型がコンパイル時に不明な場合や、汎用的なデータ処理を行う場合に特に有用です。このコミットでは、src/pkg/reflect/all_test.go
が変更されており、reflect
パッケージがfunc
型の値を扱う際のテストが、Goの比較ルールに適合するように修正されています。
インターフェースの等価性比較
Goのインターフェースは、動的な型と動的な値の2つの要素で構成されます。インターフェース値が等しいとみなされるのは、以下の両方の条件を満たす場合です。
- 動的な型が同じである: インターフェースが保持している実際の型(具象型)が同じである必要があります。
- 動的な値が等しい: インターフェースが保持している実際の値が等しい必要があります。ただし、これは基底の型が比較可能である場合にのみ行われます。
もし動的な型が異なる場合、比較結果はfalse
になります。動的な型が同じであっても、その型が比較不可能な型(スライス、マップ、関数など)である場合、それらを比較しようとするとランタイムパニックが発生します。このコミットの背景にある問題は、このランタイムパニックを確実に発生させるために、コンパイラレベルで比較を禁止する必要があったという点です。
技術的詳細
このコミットの技術的な核心は、Goコンパイラがmap
型とfunc
型をインターフェースを介した比較から明示的に除外するように変更された点にあります。
-
コンパイラレベルでの変更 (
src/cmd/gc/subr.c
):algtype
関数は、Goコンパイラ内で型の「アルゴリズムタイプ」を決定するために使用されます。これは、特定の型に対してどのような操作(例えば、等価性比較、メモリコピーなど)が許可されるかを決定する内部的な分類です。 変更前は、map
(TMAP
) とfunc
(TFUNC
) 型が、issimple
(単純な型)やisptr
(ポインタ型)などと同様に、特定の比較可能なカテゴリに含まれていました。このコミットでは、t->etype == TFUNC || t->etype == TMAP
という条件が削除され、map
とfunc
がこの比較可能なカテゴリから明示的に除外されました。 これにより、コンパイラはこれらの型がインターフェースを介して比較されることを許可せず、ランタイムでパニックを引き起こすようなコードを生成するようになります。これは、コンパイル時に比較が許可されないことを保証する重要なステップです。 -
reflect
パッケージのテスト修正 (src/pkg/reflect/all_test.go
):TestFunctionValue
というテスト関数は、func()
型の値をinterface{}
に格納し、その値をreflect.ValueOf
でラップしてテストしていました。 変更前は、v.Interface() != v.Interface() || v.Interface() != x
という直接的な等価性比較が行われていました。これは、func
型がインターフェースを介して比較可能であるという誤った前提に基づいています。 このコミットでは、比較方法がfmt.Sprint(v.Interface()) != fmt.Sprint(x)
に変更されました。fmt.Sprint
は値の文字列表現を生成するため、これは等価性比較ではなく、値の文字列表現が同じであるかどうかのチェックになります。これにより、func
型の直接比較が禁止されたGoのセマンティクスにテストが適合するようになりました。 -
新しいテストケースの追加 (
test/interface/noeq.go
): この新しいテストファイルは、map
型、struct{}
型(フィールドがないため比較可能)、func
型がインターフェースに格納された場合に、等価性比較がどのように振る舞うかを検証します。cmp(x interface{}) bool { return x == x }
という関数は、インターフェース値の等価性比較を行います。noCmp(x interface{})
関数は、cmp(x)
を呼び出し、その呼び出しがパニックを引き起こすことをshouldPanic
関数で検証します。 このテストは、map
とfunc
がインターフェースを介して比較された場合に、期待通りにパニックが発生することを確認します。struct{}
は比較可能なので、noCmp(s)
はパニックを引き起こしません(テストコードではnoCmp(s)
もパニックを期待しているように見えますが、これはstruct{}
が比較可能であるというGoのルールと矛盾します。ただし、このコミットの主眼はmap
とfunc
にあります)。
これらの変更により、Go言語はmap
とfunc
の等価性比較に関する一貫したルールを適用し、インターフェースを介した比較も禁止することで、予期せぬ動作やバグを防ぎます。
コアとなるコードの変更箇所
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -499,8 +499,7 @@ algtype(Type *t)
{
int a;
- if(issimple[t->etype] || isptr[t->etype] ||
- t->etype == TCHAN || t->etype == TFUNC || t->etype == TMAP) {
+ if(issimple[t->etype] || isptr[t->etype] || t->etype == TCHAN) {
if(t->width == 1)
a = AMEM8;
else if(t->width == 2)
src/pkg/reflect/all_test.go
--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -468,8 +468,8 @@ func TestInterfaceValue(t *testing.T) {
func TestFunctionValue(t *testing.T) {
var x interface{} = func() {}
v := ValueOf(x)
- if v.Interface() != v.Interface() || v.Interface() != x {
- t.Fatalf("TestFunction != itself")
+ if fmt.Sprint(v.Interface()) != fmt.Sprint(x) {
+ t.Fatalf("TestFunction returned wrong pointer")
}
assert(t, v.Type().String(), "func()")
}
test/interface/noeq.go
(新規ファイル)
// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: interface/noeq
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Interface values containing types that cannot be compared for equality.
package main
func main() {
cmp(1)
var (
m map[int]int
s struct{}
f func()
)
noCmp(m)
noCmp(s)
noCmp(f)
}
func cmp(x interface{}) bool {
return x == x
}
func noCmp(x interface{}) {
shouldPanic(func() { cmp(x) })
}
func shouldPanic(f func()) {
defer func() {
if recover() == nil {
panic("function should panic")
}
}()
f()
}
コアとなるコードの解説
src/cmd/gc/subr.c
の変更
algtype
関数は、Goコンパイラのバックエンドで、特定の型に対してどのような「アルゴリズム」を適用すべきかを決定する役割を担っています。ここでいう「アルゴリズム」とは、メモリのコピー、ゼロクリア、等価性比較などの低レベルな操作を指します。
変更前のコードでは、if(issimple[t->etype] || isptr[t->etype] || t->etype == TCHAN || t->etype == TFUNC || t->etype == TMAP)
という条件がありました。これは、issimple
(プリミティブ型など)、isptr
(ポインタ型)、TCHAN
(チャネル型)、TFUNC
(関数型)、TMAP
(マップ型)のいずれかに該当する場合、特定のメモリ操作アルゴリズム(例えば、バイト単位の比較やコピー)が適用されることを示唆しています。
このコミットでは、t->etype == TFUNC || t->etype == TMAP
の部分が削除されました。これにより、func
型とmap
型は、この条件に合致しなくなり、結果としてコンパイラはこれらの型がインターフェースを介して比較された場合に、ランタイムパニックを引き起こすようなコードを生成するようになります。これは、これらの型が「単純な」バイト比較やポインタ比較では等価性を正しく判断できないため、コンパイラがその比較を許可しないようにするための重要な変更です。
src/pkg/reflect/all_test.go
の変更
TestFunctionValue
は、reflect
パッケージが関数値を正しく扱えるかをテストするものです。
変更前は、v.Interface() != v.Interface() || v.Interface() != x
という行で、func()
型の値が格納されたインターフェースの直接比較を行っていました。Go言語のセマンティクスでは関数は比較不可能であるため、このテストはGoの設計意図と矛盾していました。
変更後、この行はif fmt.Sprint(v.Interface()) != fmt.Sprint(x)
に修正されました。fmt.Sprint
は、Goの値を人間が読める文字列形式に変換する関数です。関数値の場合、通常はそのメモリ上のアドレスや内部表現を示す文字列が生成されます。この変更により、テストは関数の「等価性」を直接比較するのではなく、その文字列表現が同じであるかを確認するようになりました。これは、関数が比較不可能であるというGoのルールに準拠しつつ、テストの目的(reflect
パッケージが関数値を正しく取得できるか)を達成するための適切な修正です。
test/interface/noeq.go
の新規追加
このファイルは、map
型とfunc
型がインターフェースを介して比較された場合に、ランタイムパニックが発生することを検証するための新しい統合テストです。
cmp(x interface{}) bool { return x == x }
: この関数は、任意のインターフェース値x
を受け取り、x == x
という比較を行います。Goのインターフェースの等価性ルールに従い、基底の型が比較可能であれば値の比較が行われます。noCmp(x interface{})
: この関数は、cmp(x)
を呼び出し、その呼び出しがパニックを引き起こすことを期待します。shouldPanic(f func())
: このヘルパー関数は、引数として渡された関数f
を実行し、それがパニックを引き起こすことを検証します。もしパニックが発生しなければ、それ自体がパニックを引き起こします。
main
関数では、map[int]int
型の変数m
とfunc()
型の変数f
を宣言し、それぞれをnoCmp
関数に渡しています。これにより、map
とfunc
がインターフェースに格納された状態で等価性比較が行われると、ランタイムパニックが発生することがテストされます。
この新しいテストは、コンパイラとランタイムの変更が期待通りに機能し、map
とfunc
のインターフェースを介した比較が正しく禁止されていることを保証します。
関連リンク
- Go言語の仕様: https://go.dev/ref/spec (特に "Comparison operators" のセクション)
- Go言語の
reflect
パッケージ: https://pkg.go.dev/reflect - Go言語の
fmt
パッケージ: https://pkg.go.dev/fmt
参考にした情報源リンク
- https://medium.com/ (Goにおけるマップの比較に関する記事)
- https://labex.io/ (Goにおけるマップと関数の比較に関する記事)
- https://mohitkhare.com/ (Goにおけるマップの比較に関する記事)
- https://techwasti.com/ (Goにおけるマップの比較に関する記事)
- https://stackoverflow.com/ (Goにおける
reflect.DeepEqual
、関数の比較、インターフェースの比較に関する議論) - https://geeksforgeeks.org/ (Goにおける
reflect.DeepEqual
に関する記事) - https://w3schools.com/ (Goにおけるマップキーの比較可能性に関する記事)
- https://go101.org/ (Goにおけるインターフェースの比較に関する詳細)
- https://go.dev/ (Go言語の公式ドキュメント、
comparable
インターフェースに関する情報) - https://boldlygo.tech/ (Goにおける
comparable
インターフェースに関する記事)