[インデックス 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 == a2
DeepEqual
関数のドキュメンテーションコメントが更新され、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が