[インデックス 12197] ファイルの概要
このコミットは、Go言語の標準ライブラリである reflect パッケージ内の DeepEqual 関数の挙動を修正するものです。具体的には、関数型(Func)の値を比較する際に発生していたパニック(runtime panic)を解消し、関数の比較ロジックを改善しています。
変更されたファイルは以下の2つです。
src/pkg/reflect/all_test.go:reflect.DeepEqualのテストケースが追加されています。関数型の比較に関する新しいテストが導入され、修正後のDeepEqualの挙動が期待通りであることを検証します。src/pkg/reflect/deepequal.go:reflect.DeepEqualの主要なロジックが記述されているファイルです。このファイルで、関数型の比較に関するパニックを回避し、正しい比較ロジックを実装するための変更が行われています。
コミット
commit 3a1c226a38fd3f93598c3aabc57e3acd4a764bba
Author: Rob Pike <r@golang.org>
Date: Fri Feb 24 16:25:39 2012 +1100
reflect.DeepEqual: don't panic comparing functions
Functions are equal iff they are both nil.
Fixes #3122.
R=golang-dev, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/5693057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3a1c226a38fd3f93598c3aabc57e3acd4a764bba
元コミット内容
reflect.DeepEqual: don't panic comparing functions
Functions are equal iff they are both nil.
Fixes #3122.
R=golang-dev, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/5693057
変更の背景
このコミットの背景には、Go言語の reflect パッケージにおける DeepEqual 関数の既存のバグがあります。具体的には、Issue 3122「reflect.DeepEqual panics on function comparisons」で報告された問題に対応しています。
Go言語では、関数はファーストクラスオブジェクトであり、変数に代入したり、引数として渡したり、戻り値として返したりすることができます。しかし、Goの言語仕様では、関数はポインタ型と同様に、nil との比較を除いて直接的な等価性比較(== 演算子)ができません。これは、関数の実体がメモリ上のどこに配置されるか、あるいはクロージャのキャプチャする環境など、その内部状態が複雑であるため、単純な値の比較では意味のある等価性を定義できないためです。
reflect.DeepEqual は、Goの組み込みの == 演算子では比較できない複雑なデータ構造(スライス、マップ、構造体など)の「深い」等価性を再帰的に比較するために設計されています。しかし、関数型を DeepEqual で比較しようとすると、内部的に未定義の操作が行われ、ランタイムパニックが発生するという問題がありました。これは、DeepEqual が関数型を適切に処理するためのロジックを持っていなかったためです。
このパニックは、開発者が意図せず関数を DeepEqual の引数として渡してしまった場合に、アプリケーションがクラッシュする原因となるため、早急な修正が必要とされていました。このコミットは、この問題を解決し、DeepEqual が関数型を安全に処理できるようにすることを目的としています。
前提知識の解説
reflect パッケージ
Go言語の reflect パッケージは、実行時にプログラムの構造を検査(リフレクション)するための機能を提供します。これにより、型情報(Type)、値情報(Value)、メソッド情報などを動的に取得・操作することができます。
reflect.Type: Goの型システムにおける型を表します。例えば、int、string、struct{}、func()など、あらゆるGoの型がreflect.Typeオブジェクトとして表現されます。reflect.Value: Goの変数の実行時の値を表します。reflect.ValueOf(x)を使うと、任意のGoの変数xのreflect.Valueを取得できます。このValueオブジェクトを通じて、その値の型(Type())、種類(Kind())、そしてその値自体(Interface()) にアクセスできます。reflect.Kind:reflect.Typeが表す型の「種類」を定義する列挙型です。例えば、Int、String、Struct、Slice、Map、そしてこのコミットで重要なFuncなどがあります。
reflect.DeepEqual 関数
reflect.DeepEqual(a1, a2 interface{}) bool は、2つの引数 a1 と a2 が「深い」等価性を持つかどうかを判定する関数です。
-
浅い比較 vs. 深い比較:
- 浅い比較:
==演算子による比較は「浅い比較」です。これは、プリミティブ型(int,string,boolなど)やポインタ、チャネル、関数、インターフェース、マップ(キーと値のペアが同じオブジェクトを指しているか)に対しては値の比較を行いますが、スライスや配列、構造体などの複合型に対しては、それらの要素やフィールドの内容までは比較しません。例えば、2つのスライスが同じ要素を持っていても、異なるメモリ領域に存在すれば==ではfalseになります。 - 深い比較:
DeepEqualは、スライス、マップ、構造体などの複合型に対して、その内部の要素やフィールドまで再帰的に比較します。これにより、内容が完全に一致するかどうかを判断できます。
- 浅い比較:
-
関数の比較における
DeepEqualの課題: 前述の通り、Goの関数はnilとの比較を除いて==演算子で直接比較できません。DeepEqualは、内部でreflect.Valueを使って値の種類(Kind)を判別し、それぞれの種類に応じた比較ロジックを適用します。関数型(reflect.Func)の場合、以前の実装ではこの比較ロジックが不完全であったため、パニックを引き起こしていました。
Go言語における関数の等価性
Go言語では、関数は参照型の一種と見なすことができます。2つの関数が等しいとされるのは、両方が nil である場合のみです。nil でない関数値は、たとえ同じコードを指していても、異なるクロージャ環境を持つ可能性があるため、== 演算子では比較できません。このコミットの修正は、このGo言語の関数の等価性に関する基本的な原則に則っています。
技術的詳細
このコミットの技術的詳細は、reflect.DeepEqual の内部実装、特に deepValueEqual 関数が reflect.Func 型をどのように処理するかという点に集約されます。
deepValueEqual 関数は、2つの reflect.Value オブジェクト v1 と v2 を受け取り、それらが深い等価性を持つかどうかを再帰的に判定します。この関数は、v1.Kind() を使って値の種類を判別し、それぞれの種類に応じた比較ロジックに分岐します。
修正前のコードでは、Func 型に対する明示的な case 文が存在せず、おそらく default ケースで valueInterface(v1, false) == valueInterface(v2, false) のような一般的な等価性比較が試みられていたと考えられます。しかし、reflect.Value から関数インターフェースを取得し、それを == で比較しようとすると、Goの言語仕様上、nil でない関数同士の比較は許可されていないため、ランタイムパニック(具体的には「invalid operation: function value == function value」のようなエラー)が発生していました。
このコミットでは、以下の変更によってこの問題を解決しています。
Func型の明示的なハンドリング:deepValueEqual関数内にcase Func:という新しい分岐が追加されました。これにより、DeepEqualが関数型を検出した際に、専用のロジックで処理できるようになります。nil関数の比較ロジック:case Func:ブロック内で、まずv1.IsNil()とv2.IsNil()をチェックします。Goの関数はnilとの比較のみが許可されているため、両方の関数がnilであればtrueを返します。これは、nil関数は等しいと見なされるというGoの言語仕様に合致しています。nilでない関数の比較:v1.IsNil() && v2.IsNil()がfalseの場合、つまり少なくともどちらか一方がnilでない関数である場合、DeepEqualはfalseを返します。これは、nilでない関数同士は、たとえ同じコードを指していても、Goの言語仕様上「等しい」とは見なされないという原則に基づいています。DeepEqualは、関数の内部状態(クロージャのキャプチャした変数など)を比較するような高度な機能は提供せず、Goの基本的な等価性ルールに従います。コメント// Can't do better than this:は、これ以上深い比較は不可能または意味がないことを示唆しています。
これらの変更により、DeepEqual は関数型を安全に処理できるようになり、パニックを回避しつつ、Goの言語仕様に準拠した関数の等価性比較(両方 nil の場合のみ true)を提供するようになりました。
また、src/pkg/reflect/all_test.go には、この修正を検証するための新しいテストケースが追加されています。具体的には、nil 関数同士の比較、nil 関数と非nil 関数の比較、および非nil 関数同士の比較のテストが含まれており、修正後の DeepEqual が期待通りの結果を返すことを保証しています。
コアとなるコードの変更箇所
src/pkg/reflect/all_test.go
--- a/src/pkg/reflect/all_test.go
+++ b/src/pkg/reflect/all_test.go
@@ -629,6 +629,13 @@ type DeepEqualTest struct {
eq bool
}
+// Simple functions for DeepEqual tests.
+var (
+ fn1 func() // nil.
+ fn2 func() // nil.
+ fn3 = func() { fn1() } // Not nil.
+)
+
var deepEqualTests = []DeepEqualTest{
// Equalities
{1, 1, true},
@@ -641,6 +648,7 @@ var deepEqualTests = []DeepEqualTest{
{Basic{1, 0.5}, Basic{1, 0.5}, true},
{error(nil), error(nil), true},
{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
+ {fn1, fn2, true},
// Inequalities
{1, 2, false},
@@ -658,6 +666,8 @@ var deepEqualTests = []DeepEqualTest{
{map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false},
{nil, 1, false},
{1, nil, false},
+ {fn1, fn3, false},
+ {fn3, fn3, false},
// Nil vs empty: not the same.
{[]int{}, []int(nil), false},
src/pkg/reflect/deepequal.go
--- a/src/pkg/reflect/deepequal.go
+++ b/src/pkg/reflect/deepequal.go
@@ -108,6 +108,12 @@ func deepValueEqual(v1, v2 Value, visited map[uintptr]*visit, depth int) (b bool
}
}
return true
+ case Func:
+ if v1.IsNil() && v2.IsNil() {
+ return true
+ }
+ // Can't do better than this:
+ return false
default:
// Normal equality suffices
return valueInterface(v1, false) == valueInterface(v2, false)
@@ -117,8 +123,8 @@ func deepValueEqual(v1, v2 Value, visited map[uintptr]*visit, depth int) (b bool
}
// DeepEqual tests for deep equality. It uses normal == equality where possible
-// but will scan members of arrays, slices, and fields of structs. It correctly
-// handles recursive types.
+// but will scan members of arrays, slices, maps, and fields of structs. It correctly
+// handles recursive types. Functions are equal only if they are both nil.
func DeepEqual(a1, a2 interface{}) bool {
if a1 == nil || a2 == nil {
return a1 == a2
コアとなるコードの解説
src/pkg/reflect/all_test.go の変更点
このファイルでは、DeepEqual のテストスイート deepEqualTests に、関数型の比較に関する新しいテストケースが追加されています。
-
fn1,fn2,fn3の定義:var ( fn1 func() // nil. fn2 func() // nil. fn3 = func() { fn1() } // Not nil. )fn1とfn2はnilの関数変数として宣言されています。fn3は匿名関数を代入しており、これはnilではない具体的な関数値です。これらの変数が、様々な関数の比較シナリオをテストするために使用されます。 -
新しいテストケースの追加:
{fn1, fn2, true}: 両方nilの関数は等しいと判定されるべきです。{fn1, fn3, false}:nil関数と非nil関数は等しくないと判定されるべきです。{fn3, fn3, false}: 同じ非nil関数同士であっても、Goの言語仕様上は等しいと判定されないべきです。これは、関数が参照型であり、nilでない限り==で比較できないという原則に基づいています。DeepEqualはこの原則に従います。
これらのテストケースは、deepequal.go で行われた修正が、関数型の比較において期待通りの挙動を示すことを検証します。
src/pkg/reflect/deepequal.go の変更点
このファイルでは、deepValueEqual 関数と DeepEqual 関数のコメントが変更されています。
-
deepValueEqual関数のcase Func:ブロックの追加:case Func: if v1.IsNil() && v2.IsNil() { return true } // Can't do better than this: return falseこれがこのコミットの最も重要な変更点です。
case Func::v1のKindがFunc(関数型)である場合にこのブロックが実行されます。if v1.IsNil() && v2.IsNil():reflect.ValueのIsNil()メソッドは、その値がnilであるかどうかをチェックします。関数型の場合、これは関数変数がnilであるかどうかを意味します。両方の関数がnilであれば、Goの言語仕様に従ってtrueを返します。return false: 上記の条件が満たされない場合、つまり少なくともどちらか一方がnilでない関数である場合、falseを返します。これは、nilでない関数同士は等しいと見なされないというGoの原則を反映しています。コメント// Can't do better than this:は、関数の内部状態を比較して「深い」等価性を判断することは、Goの言語レベルではサポートされておらず、DeepEqualのスコープ外であることを示唆しています。
-
DeepEqual関数のコメントの更新:--- a/src/pkg/reflect/deepequal.go +++ b/src/pkg/reflect/deepequal.go @@ -117,8 +123,8 @@ func deepValueEqual(v1, v2 Value, visited map[uintptr]*visit, depth int) (b bool } // DeepEqual tests for deep equality. It uses normal == equality where possible -// but will scan members of arrays, slices, and fields of structs. It correctly -// handles recursive types. +// but will scan members of arrays, slices, maps, and fields of structs. It correctly +// handles recursive types. Functions are equal only if they are both nil. func DeepEqual(a1, a2 interface{}) bool { if a1 == nil || a2 == nil { return a1 == a2DeepEqual関数のドキュメンテーションコメントが更新され、mapsがスキャン対象に追加されたことと、「Functions are equal only if they are both nil.」 という関数比較の新しいルールが明記されました。これにより、DeepEqualの挙動がより明確になります。
これらの変更により、reflect.DeepEqual は関数型を安全かつGoの言語仕様に準拠した形で比較できるようになり、以前のパニックの問題が解決されました。
関連リンク
- GitHub Issue #3122: https://github.com/golang/go/issues/3122
- Go Code Review (CL) 5693057: https://golang.org/cl/5693057
参考にした情報源リンク
- Web search results for "Golang issue 3122": https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFOCdUuqZz-uWmzE2hJUWJQ1qYDKeEyDTasq4iHypxtApOXh6nOd47UWqe5F2u2u1CdYkmpVKAfaXKzfg-XvMzs3OsywiaGoBUkipHv92oBOZuP1isnuoukikibPcxN3lvzEIU=
- この検索結果は、Issue 3122が
reflect.DeepEqualが関数比較でパニックを起こす問題であることを確認するために使用しました。
- この検索結果は、Issue 3122が