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

[インデックス 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(実装修正)

変更内容の詳細

  1. DeepEqualのSliceとMapの比較処理で、IsNil()メソッドを使用したnilチェックを追加
  2. 対応するテストケースを追加して、新しい動作を検証

変更の背景

このコミットが行われた背景には、Go言語におけるnilと空のslice/mapの意味的な違いを正しく反映させる必要があったことがあります。

初期のreflect.DeepEqual実装では、nilのsliceや空のsliceを同じものとして扱っていました。しかし、これは以下の理由で問題でした:

  1. セマンティクスの違い: nilのsliceは未初期化状態を表し、空のsliceは初期化されているが要素が0個の状態を表します
  2. メモリ表現の違い: nilのsliceはメモリ上でゼロ値として表現され、空のsliceは実際のsliceヘッダーを持ちます
  3. 実用性の問題: 開発者が意図的に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チェックが不十分でした。具体的には:

  1. Sliceの比較:長さが0の場合、nilスライスと空スライスを同じものとして扱っていました
  2. 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になります:

  1. v1がnilでv2がnilでない場合
  2. v1がnilでなくv2がnilの場合

どちらの場合も、2つの値は等しくないため、falseを返します。

テストケースの設計

追加されたテストケースは、以下の組み合わせを網羅しています:

  1. 空のslice vs nilスライス[]int{}[]int(nil)は等しくない
  2. 空のslice vs 空のslice[]int{}[]int{}は等しい
  3. nilスライス vs nilスライス[]int(nil)[]int(nil)は等しい
  4. 空のmap vs nilマップmap[int]int{}map[int]int(nil)は等しくない
  5. 空のmap vs 空のmapmap[int]int{}map[int]int{}は等しい
  6. nilマップ vs nilマップmap[int]int(nil)map[int]int(nil)は等しい

処理の順序

修正後のコードでは、以下の順序で比較が行われます:

  1. nilチェック:まず両方の値のnil状態を確認
  2. 長さチェック:nil状態が同じ場合、次に長さを比較
  3. 要素チェック:長さが同じ場合、個々の要素を比較

この順序により、効率的かつ正確な比較が可能になります。

関連リンク

参考にした情報源リンク