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

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

このコミットは、Go言語のコンパイラ(gc)と標準ライブラリのreflectパッケージにおける変更を扱っています。具体的には、map型とfunc型がインターフェースを介して比較された場合に、等価性チェックを許可しないようにする修正です。これは、以前のコミットで直接的なmapfuncの等価性比較が削除された際に、インターフェースを介した比較が見落とされていたことに対する対応です。

コミット

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コンパイラはmapfuncの直接的な等価性比較を既に禁止していました。しかし、これらの型がinterface{}(空インターフェース)にラップされた場合、インターフェースの等価性比較ルール(動的な型と値が等しい場合に等しい)が適用され、結果的にmapfuncの比較が意図せず行われてしまう可能性がありました。

このコミットは、この「見落とし」を修正し、mapfuncがインターフェースを介して比較された場合でも、ランタイムパニックを引き起こすことで、その比較を明示的に禁止することを目的としています。これにより、Go言語の型システムにおけるmapfuncの比較に関する一貫性が保たれます。

前提知識の解説

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つの要素で構成されます。インターフェース値が等しいとみなされるのは、以下の両方の条件を満たす場合です。

  1. 動的な型が同じである。
  2. 動的な値が等しい(基底の型が比較可能である場合)。

このコミットの背景にある問題は、mapfuncといった比較不可能な型がインターフェースに格納された場合、インターフェースの等価性比較ルールが適用され、結果的に比較不可能な型が比較されてしまうという点でした。Goのランタイムは、このような比較が行われた際にパニックを引き起こすことで、不正な操作を防止します。

技術的詳細

このコミットの技術的な核心は、Goコンパイラがmap型とfunc型をインターフェースを介した比較から除外するように変更された点にあります。

  1. コンパイラレベルでの変更 (src/cmd/gc/subr.c): algtype関数は、Goコンパイラ内で型の「アルゴリズムタイプ」を決定するために使用されると考えられます。これは、特定の型に対してどのような操作(例えば、等価性比較、メモリコピーなど)が許可されるかを決定する内部的な分類です。 変更前は、map (TMAP) と func (TFUNC) 型が、issimple(単純な型)やisptr(ポインタ型)などと同様に、特定の比較可能なカテゴリに含まれていました。このコミットでは、t->etype == TFUNC || t->etype == TMAPという条件が削除され、mapfuncがこの比較可能なカテゴリから明示的に除外されました。 これにより、コンパイラはこれらの型がインターフェースを介して比較されることを許可せず、ランタイムでパニックを引き起こすようなコードを生成するようになります。

  2. 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のセマンティクスにテストが適合するようになりました。

  3. 新しいテストケースの追加 (test/interface/noeq.go): この新しいテストファイルは、map型、struct{}型(フィールドがないため比較可能)、func型がインターフェースに格納された場合に、等価性比較がどのように振る舞うかを検証します。 cmp(x interface{}) bool { return x == x }という関数は、インターフェース値の等価性比較を行います。 noCmp(x interface{})関数は、cmp(x)を呼び出し、その呼び出しがパニックを引き起こすことをshouldPanic関数で検証します。 このテストは、mapfuncがインターフェースを介して比較された場合に、期待通りにパニックが発生することを確認します。struct{}は比較可能なので、noCmp(s)はパニックを引き起こしません(テストコードではnoCmp(s)もパニックを期待しているように見えますが、これはstruct{}が比較可能であるというGoのルールと矛盾します。ただし、このコミットの主眼はmapfuncにあります)。

これらの変更により、Go言語はmapfuncの等価性比較に関する一貫したルールを適用し、インターフェースを介した比較も禁止することで、予期せぬ動作やバグを防ぎます。

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

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型の変数mfunc()型の変数fを宣言し、それぞれをnoCmp関数に渡しています。これにより、mapfuncがインターフェースに格納された状態で等価性比較が行われると、ランタイムパニックが発生することがテストされます。

この新しいテストは、コンパイラとランタイムの変更が期待通りに機能し、mapfuncのインターフェースを介した比較が正しく禁止されていることを保証します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go言語のIssueトラッカーやメーリングリスト(golang-dev)の議論
  • Go言語における型の比較に関するブログ記事やチュートリアル

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

このコミットは、Go言語のコンパイラ(gc)と標準ライブラリのreflectパッケージにおける重要な修正を導入しています。主な目的は、map型とfunc型がインターフェースを介して比較された場合に、その等価性チェックを明示的に禁止することです。これは、以前のコミットでmapfuncの直接的な等価性比較が削除された際に、インターフェースを介した比較という「抜け穴」が見落とされていたことに対する対応であり、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コンパイラはmapfuncの直接的な等価性比較を既に禁止していました。しかし、これらの比較不可能な型がinterface{}(空インターフェース)にラップされた場合、インターフェースの等価性比較ルール(動的な型と値が等しい場合に等しい)が適用され、結果的にmapfuncの比較が意図せず行われてしまうという「抜け穴」が存在していました。Goのランタイムは、比較不可能な型がインターフェースを介して比較された場合、ランタイムパニックを引き起こすことで不正な操作を防止します。

このコミットは、この「見落とし」を修正し、mapfuncがインターフェースを介して比較された場合でも、コンパイラがその比較を許可しないように変更することで、ランタイムパニックを確実に発生させるようにします。これにより、Go言語の型システムにおけるmapfuncの比較に関する一貫性が保たれ、開発者が予期せぬ動作に遭遇するのを防ぎます。

前提知識の解説

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つの要素で構成されます。インターフェース値が等しいとみなされるのは、以下の両方の条件を満たす場合です。

  1. 動的な型が同じである: インターフェースが保持している実際の型(具象型)が同じである必要があります。
  2. 動的な値が等しい: インターフェースが保持している実際の値が等しい必要があります。ただし、これは基底の型が比較可能である場合にのみ行われます。

もし動的な型が異なる場合、比較結果はfalseになります。動的な型が同じであっても、その型が比較不可能な型(スライス、マップ、関数など)である場合、それらを比較しようとするとランタイムパニックが発生します。このコミットの背景にある問題は、このランタイムパニックを確実に発生させるために、コンパイラレベルで比較を禁止する必要があったという点です。

技術的詳細

このコミットの技術的な核心は、Goコンパイラがmap型とfunc型をインターフェースを介した比較から明示的に除外するように変更された点にあります。

  1. コンパイラレベルでの変更 (src/cmd/gc/subr.c): algtype関数は、Goコンパイラ内で型の「アルゴリズムタイプ」を決定するために使用されます。これは、特定の型に対してどのような操作(例えば、等価性比較、メモリコピーなど)が許可されるかを決定する内部的な分類です。 変更前は、map (TMAP) と func (TFUNC) 型が、issimple(単純な型)やisptr(ポインタ型)などと同様に、特定の比較可能なカテゴリに含まれていました。このコミットでは、t->etype == TFUNC || t->etype == TMAPという条件が削除され、mapfuncがこの比較可能なカテゴリから明示的に除外されました。 これにより、コンパイラはこれらの型がインターフェースを介して比較されることを許可せず、ランタイムでパニックを引き起こすようなコードを生成するようになります。これは、コンパイル時に比較が許可されないことを保証する重要なステップです。

  2. 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のセマンティクスにテストが適合するようになりました。

  3. 新しいテストケースの追加 (test/interface/noeq.go): この新しいテストファイルは、map型、struct{}型(フィールドがないため比較可能)、func型がインターフェースに格納された場合に、等価性比較がどのように振る舞うかを検証します。 cmp(x interface{}) bool { return x == x }という関数は、インターフェース値の等価性比較を行います。 noCmp(x interface{})関数は、cmp(x)を呼び出し、その呼び出しがパニックを引き起こすことをshouldPanic関数で検証します。 このテストは、mapfuncがインターフェースを介して比較された場合に、期待通りにパニックが発生することを確認します。struct{}は比較可能なので、noCmp(s)はパニックを引き起こしません(テストコードではnoCmp(s)もパニックを期待しているように見えますが、これはstruct{}が比較可能であるというGoのルールと矛盾します。ただし、このコミットの主眼はmapfuncにあります)。

これらの変更により、Go言語はmapfuncの等価性比較に関する一貫したルールを適用し、インターフェースを介した比較も禁止することで、予期せぬ動作やバグを防ぎます。

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

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型の変数mfunc()型の変数fを宣言し、それぞれをnoCmp関数に渡しています。これにより、mapfuncがインターフェースに格納された状態で等価性比較が行われると、ランタイムパニックが発生することがテストされます。

この新しいテストは、コンパイラとランタイムの変更が期待通りに機能し、mapfuncのインターフェースを介した比較が正しく禁止されていることを保証します。

関連リンク

参考にした情報源リンク