[インデックス 10391] ファイルの概要
コミット
commit 4e65478cbd9691aefdcf0c2f636d4909c8a45993
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 14 16:11:15 2011 -0500
reflect: empty slice/map is not DeepEqual to nil
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5373095
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e65478cbd9691aefdcf0c2f636d4909c8a45993
元コミット内容
このコミットは、Go言語のreflectパッケージにおけるDeepEqual関数の動作を修正したものです。具体的には、空のsliceやmapとnilを区別して比較するようになりました。
変更されたファイル:
- src/pkg/reflect/all_test.go(テストケース追加)
- src/pkg/reflect/deepequal.go(実装修正)
変更内容の詳細:
- DeepEqualのSliceとMapの比較処理で、IsNil()メソッドを使用したnilチェックを追加
- 対応するテストケースを追加して、新しい動作を検証
変更の背景
このコミットが行われた背景には、Go言語におけるnilと空のslice/mapの意味的な違いを正しく反映させる必要があったことがあります。
初期のreflect.DeepEqual実装では、nilのsliceや空のsliceを同じものとして扱っていました。しかし、これは以下の理由で問題でした:
- セマンティクスの違い: nilのsliceは未初期化状態を表し、空のsliceは初期化されているが要素が0個の状態を表します
- メモリ表現の違い: nilのsliceはメモリ上でゼロ値として表現され、空のsliceは実際のsliceヘッダーを持ちます
- 実用性の問題: 開発者が意図的にnilと空のsliceを区別して使用している場合、DeepEqualがそれらを同じものとして扱うのは期待に反します
この修正により、Go言語のDeepEqualは、nilスライスと空スライス、nilマップと空マップを明確に区別するようになりました。
前提知識の解説
Go言語におけるnilと空のslice/mapの違い
nilスライス:
var s []int // s == nil, len(s) == 0, cap(s) == 0
空のスライス:
s := []int{} // s != nil, len(s) == 0, cap(s) == 0
s := make([]int, 0) // s != nil, len(s) == 0, cap(s) == 0
nilマップ:
var m map[string]int // m == nil, len(m) == 0
空のマップ:
m := map[string]int{} // m != nil, len(m) == 0
m := make(map[string]int) // m != nil, len(m) == 0
reflect.DeepEqual関数の役割
reflect.DeepEqual
は、Go言語において構造体やスライス、マップなどの複合型の深い比較を行う関数です。通常の==
演算子では比較できない複雑なデータ構造を、その内容まで含めて比較することができます。
reflect.Value.IsNil()メソッド
IsNil()
メソッドは、reflect.Valueが表現する値がnilかどうかを判定します。ただし、このメソッドは以下の型に対してのみ呼び出すことができます:
- chan
- func
- interface
- map
- pointer
- slice
他の型に対して呼び出すとパニックが発生します。
技術的詳細
修正前の問題
修正前のDeepEqual実装では、SliceとMapの比較において、nilチェックが不十分でした。具体的には:
- Sliceの比較:長さが0の場合、nilスライスと空スライスを同じものとして扱っていました
- Mapの比較:同様に、長さが0の場合、nilマップと空マップを同じものとして扱っていました
修正後の動作
修正後は、比較の最初の段階でIsNil()メソッドを使用して、両方の値のnil状態を確認します:
// Sliceの場合
if v1.IsNil() != v2.IsNil() {
return false
}
// Mapの場合
if v1.IsNil() != v2.IsNil() {
return false
}
この修正により、以下の動作が確保されます:
- nilスライス同士は等しい
- 空スライス同士は等しい
- nilスライスと空スライスは等しくない
- nilマップ同士は等しい
- 空マップ同士は等しい
- nilマップと空マップは等しくない
コアとなるコードの変更箇所
src/pkg/reflect/deepequal.go
Sliceの比較部分:
case Slice:
+ if v1.IsNil() != v2.IsNil() {
+ return false
+ }
if v1.Len() != v2.Len() {
return false
}
Mapの比較部分:
case Map:
+ if v1.IsNil() != v2.IsNil() {
+ return false
+ }
if v1.Len() != v2.Len() {
return false
}
src/pkg/reflect/all_test.go
追加されたテストケース:
// Nil vs empty: not the same.
{[]int{}, []int(nil), false},
{[]int{}, []int{}, true},
{[]int(nil), []int(nil), true},
{map[int]int{}, map[int]int(nil), false},
{map[int]int{}, map[int]int{}, true},
{map[int]int(nil), map[int]int(nil), true},
コアとなるコードの解説
nilチェックの実装
追加されたコードの核心は、IsNil()メソッドを使用した比較です:
if v1.IsNil() != v2.IsNil() {
return false
}
この条件は以下の状況でtrue
になります:
v1
がnilでv2
がnilでない場合v1
がnilでなくv2
がnilの場合
どちらの場合も、2つの値は等しくないため、false
を返します。
テストケースの設計
追加されたテストケースは、以下の組み合わせを網羅しています:
- 空のslice vs nilスライス:
[]int{}
と[]int(nil)
は等しくない - 空のslice vs 空のslice:
[]int{}
と[]int{}
は等しい - nilスライス vs nilスライス:
[]int(nil)
と[]int(nil)
は等しい - 空のmap vs nilマップ:
map[int]int{}
とmap[int]int(nil)
は等しくない - 空のmap vs 空のmap:
map[int]int{}
とmap[int]int{}
は等しい - nilマップ vs nilマップ:
map[int]int(nil)
とmap[int]int(nil)
は等しい
処理の順序
修正後のコードでは、以下の順序で比較が行われます:
- nilチェック:まず両方の値のnil状態を確認
- 長さチェック:nil状態が同じ場合、次に長さを比較
- 要素チェック:長さが同じ場合、個々の要素を比較
この順序により、効率的かつ正確な比較が可能になります。
関連リンク
参考にした情報源リンク
- golang/go Issue #4133 - reflect: document that DeepEqual distinguishes nil and empty slice
- golang/go Issue #16531 - reflect: DeepEqual treats nil and empty map as not equal
- golang-nuts Google Groups - Nil / empty slice equality
- GeeksforGeeks - reflect.IsNil() Function in Golang
- golang/go Issue #51649 - reflect: reflect.ValueOf(nil).IsNil() panics
- Stack Overflow - How to check reflect.Value is nil or not?