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

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

このコミットは、Go言語の標準ライブラリ reflect パッケージ内の DeepEqual 関数のドキュメントを更新するものです。具体的には、DeepEqual が空のスライスとnilスライスを区別するという挙動について、明示的に記述を追加しています。

コミット

commit 74a7cc9bf2fcf034c5de92b9217e7fab51880e73
Author: Rob Pike <r@golang.org>
Date:   Wed Sep 26 15:21:07 2012 +1000

    reflect.DeepEqual: document that empty and nil are unequal for slices
    
    Update #4133.
    Added a sentence of documentation to call out the behavior.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/6572051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/74a7cc9bf2fcf034c5de92b9217e7fab51880e73

元コミット内容

reflect.DeepEqual: document that empty and nil are unequal for slices
    
Update #4133.
Added a sentence of documentation to call out the behavior.

変更の背景

このコミットの背景には、Go言語におけるスライス(slice)の「nil」と「空(empty)」の概念、そしてそれらが reflect.DeepEqual 関数でどのように扱われるかについての、ユーザーの誤解や混乱があったと考えられます。

Go言語において、スライスは内部的にポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。

  • nilスライス: var s []int のように宣言された直後のスライスはnilスライスです。この場合、内部ポインタはnilであり、長さも容量も0です。nilスライスは、メモリを一切割り当てていない状態を示します。
  • 空スライス: make([]int, 0)[]int{} のように作成されたスライスは空スライスです。この場合、内部ポインタは有効なメモリを指していますが、長さは0です。容量は0またはそれ以上になることがあります。空スライスは、要素を持たないが、有効な基盤配列を持つスライスを示します。

通常のGoの比較演算子 == を使用してスライスを比較することはできません(スライスは比較可能ではないため、コンパイルエラーになります)。しかし、reflect.DeepEqual は、2つの値が「深く」等しいかどうかを再帰的にチェックする関数です。

多くのプログラマーは、nilスライスと空スライスが論理的に同じ「要素がない」状態を表すため、DeepEqual で比較した場合に true になると期待するかもしれません。しかし、DeepEqual は内部的な表現(ポインタ、長さ、容量)も考慮するため、nilスライスと空スライスは異なるものとして扱われます。

この挙動は、特にGo言語に慣れていない開発者にとっては直感的ではない場合があり、バグの原因となる可能性がありました。そのため、このコミットでは、DeepEqual のドキュメントにこの重要な挙動を明記することで、開発者が混乱することなく、より正確に DeepEqual を使用できるようにすることを目的としています。

コミットメッセージにある #4133 は、この問題に関連するGoのIssueトラッカーのエントリを指しています。これは、この挙動に関する議論や報告があったことを示唆しています。

前提知識の解説

Go言語のスライス (Slice)

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、長さ(len)と容量(cap)を持ちます。

  • 長さ (len): スライスに含まれる要素の数。
  • 容量 (cap): スライスの最初の要素から、基盤となる配列の末尾までの要素の数。

スライスは、基盤となる配列へのポインタ、長さ、容量の3つのフィールドで構成されます。

type SliceHeader struct {
    Data uintptr // 基盤となる配列へのポインタ
    Len  int     // スライスの長さ
    Cap  int     // スライスの容量
}

nilスライスと空スライス:

  • nilスライス: var s []int のように宣言されたスライスはnilスライスです。この場合、s == niltrue を返します。内部的には Data ポインタは nilLen0Cap0 です。
  • 空スライス: make([]int, 0)[]int{} のように初期化されたスライスは空スライスです。この場合、s == nilfalse を返します。Len0 ですが、Data ポインタは有効なメモリを指しており、Cap0 またはそれ以上です。

この違いは、メモリ割り当ての有無や、スライスが基盤となる配列を参照しているかどうかに起因します。

reflect パッケージと DeepEqual 関数

reflect パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、型情報、フィールド、メソッドなどを動的に操作できます。

reflect.DeepEqual 関数は、Go言語で2つの値が「深く」等しいかどうかを判断するために使用されます。これは、通常の == 演算子では比較できない複雑なデータ構造(スライス、マップ、構造体など)の比較に特に役立ちます。

DeepEqual の主な特徴は以下の通りです。

  • 再帰的な比較: 構造体や配列、スライス、マップの要素を再帰的に比較します。
  • 型の一致: 比較される2つの値は同じ型である必要があります。型が異なる場合、DeepEqualfalse を返します。
  • ポインタのデリファレンス: ポインタが指す先の値を比較します。
  • 関数の比較: 関数は、両方が nil の場合にのみ等しいと見なされます。それ以外の場合は false です。
  • nilと空の区別: このコミットで明確にされたように、スライスに関してはnilスライスと空スライスを区別します。これは、内部的な表現(ポインタがnilか否か)が異なるためです。

技術的詳細

このコミットは、reflect.DeepEqual 関数の実装自体を変更するものではなく、そのドキュメンテーションを改善するものです。具体的には、src/pkg/reflect/deepequal.go ファイルの DeepEqual 関数のコメントに以下の1行が追加されました。

--- a/src/pkg/reflect/deepequal.go
+++ b/src/pkg/reflect/deepequal.go
@@ -125,6 +125,7 @@ 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, maps, and fields of structs. It correctly
 // handles recursive types. Functions are equal only if they are both nil.
+// Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice.
 func DeepEqual(a1, a2 interface{}) bool {
  if a1 == nil || a2 == nil {
  return a1 == a2

この変更は、DeepEqual の既存の挙動(nilスライスと空スライスを区別する)を明示的に文書化することで、ユーザーがこの関数の動作をより正確に理解できるようにすることを目的としています。

DeepEqual の内部実装では、スライスを比較する際に、その Data ポインタが nil であるかどうかをチェックします。nilスライスの場合、Data ポインタは nil ですが、空スライスの場合、Data ポインタは有効な(ただし長さ0の)基盤配列を指しています。このポインタの違いが、DeepEqual が両者を異なるものとして扱う理由です。

例えば、以下のコードを考えます。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var nilSlice []int
	emptySlice := []int{}
	emptySliceMake := make([]int, 0)

	fmt.Printf("nilSlice == nil: %t\n", nilSlice == nil)
	fmt.Printf("emptySlice == nil: %t\n", emptySlice == nil)
	fmt.Printf("emptySliceMake == nil: %t\n", emptySliceMake == nil)

	fmt.Printf("reflect.DeepEqual(nilSlice, emptySlice): %t\n", reflect.DeepEqual(nilSlice, emptySlice))
	fmt.Printf("reflect.DeepEqual(nilSlice, emptySliceMake): %t\n", reflect.DeepEqual(nilSlice, emptySliceMake))
	fmt.Printf("reflect.DeepEqual(emptySlice, emptySliceMake): %t\n", reflect.DeepEqual(emptySlice, emptySliceMake))
}

このコードを実行すると、reflect.DeepEqual(nilSlice, emptySlice) および reflect.DeepEqual(nilSlice, emptySliceMake)false を返します。これは、DeepEqual がnilスライスと空スライスを異なるものとして扱っていることを示しています。このコミットは、この重要な挙動をドキュメントに明記することで、開発者が予期せぬ結果に遭遇するのを防ぎます。

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

変更は src/pkg/reflect/deepequal.go ファイルの126行目に行われました。

--- a/src/pkg/reflect/deepequal.go
+++ b/src/pkg/reflect/deepequal.go
@@ -125,6 +125,7 @@ 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, maps, and fields of structs. It correctly
 // handles recursive types. Functions are equal only if they are both nil.
+// Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice.
 func DeepEqual(a1, a2 interface{}) bool {
  if a1 == nil || a2 == nil {
  return a1 == a2

具体的には、DeepEqual 関数のドキュメンテーションコメントに以下の行が追加されました。

// Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice.

コアとなるコードの解説

このコミットは、Go言語の reflect パッケージにある DeepEqual 関数のドキュメンテーションを更新するものです。DeepEqual 関数は、2つのGoの値を「深く」比較し、それらが等しいかどうかを判断します。この「深さ」とは、単に値が同じであるかだけでなく、構造体、配列、スライス、マップなどの複合型の内部要素も再帰的に比較することを意味します。

追加されたコメント // Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice. は、DeepEqual の重要な挙動を明確にしています。

  • regular == との比較: Go言語では、スライス自体を == 演算子で直接比較することはできません(コンパイルエラーになります)。しかし、このコメントは、他の型(例えば整数や文字列)の == 比較とは異なり、DeepEqual がスライスの nil と空を区別するという点を強調しています。これは、nil スライスと空スライスが、要素を持たないという点では同じように見えても、内部的な表現(基盤となる配列へのポインタが nil かどうか)が異なるためです。
  • empty slicenil slice の区別:
    • nil slice: var s []T のように宣言されたスライスは nil スライスです。このスライスは基盤となる配列を持たず、そのポインタは nil です。
    • empty slice: []T{}make([]T, 0) のように初期化されたスライスは空スライスです。このスライスは基盤となる配列を持ちますが、その長さは0です。ポインタは nil ではありません。

DeepEqual は、これらの内部的な違いを認識し、nil スライスと空スライスを異なるものとして扱います。このドキュメントの追加により、開発者は DeepEqual を使用する際に、この特定の挙動を意識し、予期せぬ結果を避けることができます。これは、Go言語の設計思想である「明示的であること」と「驚きを最小限に抑えること」に沿った改善と言えます。

関連リンク

参考にした情報源リンク

  • https://github.com/golang/go/commit/74a7cc9bf2fcf034c5de92b9217e7fab51880e73
  • Go言語の公式ドキュメントおよびブログ記事
  • Go言語のIssueトラッカー
  • reflect.DeepEqual の挙動に関する一般的なGoコミュニティの議論
  • Go言語におけるnilスライスと空スライスの違いに関する情報# [インデックス 13955] ファイルの概要

このコミットは、Go言語の標準ライブラリ reflect パッケージ内の DeepEqual 関数のドキュメントを更新するものです。具体的には、DeepEqual が空のスライスとnilスライスを区別するという挙動について、明示的に記述を追加しています。

コミット

commit 74a7cc9bf2fcf034c5de92b9217e7fab51880e73
Author: Rob Pike <r@golang.org>
Date:   Wed Sep 26 15:21:07 2012 +1000

    reflect.DeepEqual: document that empty and nil are unequal for slices
    
    Update #4133.
    Added a sentence of documentation to call out the behavior.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/6572051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/74a7cc9bf2fcf034c5de92b9217e7fab51880e73

元コミット内容

reflect.DeepEqual: document that empty and nil are unequal for slices
    
Update #4133.
Added a sentence of documentation to call out the behavior.

変更の背景

このコミットの背景には、Go言語におけるスライス(slice)の「nil」と「空(empty)」の概念、そしてそれらが reflect.DeepEqual 関数でどのように扱われるかについての、ユーザーの誤解や混乱があったと考えられます。

Go言語において、スライスは内部的にポインタ、長さ(len)、容量(cap)の3つの要素で構成されます。

  • nilスライス: var s []int のように宣言された直後のスライスはnilスライスです。この場合、内部ポインタはnilであり、長さも容量も0です。nilスライスは、メモリを一切割り当てていない状態を示します。
  • 空スライス: make([]int, 0)[]int{} のように作成されたスライスは空スライスです。この場合、内部ポインタは有効なメモリを指していますが、長さは0です。容量は0またはそれ以上になることがあります。空スライスは、要素を持たないが、有効な基盤配列を持つスライスを示します。

通常のGoの比較演算子 == を使用してスライスを比較することはできません(スライスは比較可能ではないため、コンパイルエラーになります)。しかし、reflect.DeepEqual は、2つの値が「深く」等しいかどうかを再帰的にチェックする関数です。

多くのプログラマーは、nilスライスと空スライスが論理的に同じ「要素がない」状態を表すため、DeepEqual で比較した場合に true になると期待するかもしれません。しかし、DeepEqual は内部的な表現(ポインタ、長さ、容量)も考慮するため、nilスライスと空スライスは異なるものとして扱われます。

この挙動は、特にGo言語に慣れていない開発者にとっては直感的ではない場合があり、バグの原因となる可能性がありました。そのため、このコミットでは、DeepEqual のドキュメントにこの重要な挙動を明記することで、開発者が混乱することなく、より正確に DeepEqual を使用できるようにすることを目的としています。

コミットメッセージにある #4133 は、この問題に関連するGoのIssueトラッカーのエントリを指しています。これは、この挙動に関する議論や報告があったことを示唆しています。

前提知識の解説

Go言語のスライス (Slice)

Go言語のスライスは、配列のセグメントを参照するデータ構造です。スライスは、長さ(len)と容量(cap)を持ちます。

  • 長さ (len): スライスに含まれる要素の数。
  • 容量 (cap): スライスの最初の要素から、基盤となる配列の末尾までの要素の数。

スライスは、基盤となる配列へのポインタ、長さ、容量の3つのフィールドで構成されます。

type SliceHeader struct {
    Data uintptr // 基盤となる配列へのポインタ
    Len  int     // スライスの長さ
    Cap  int     // スライスの容量
}

nilスライスと空スライス:

  • nilスライス: var s []int のように宣言されたスライスはnilスライスです。この場合、s == niltrue を返します。内部的には Data ポインタは nilLen0Cap0 です。
  • 空スライス: make([]int, 0)[]int{} のように初期化されたスライスは空スライスです。この場合、s == nilfalse を返します。Len0 ですが、Data ポインタは有効なメモリを指しており、Cap0 またはそれ以上です。

この違いは、メモリ割り当ての有無や、スライスが基盤となる配列を参照しているかどうかに起因します。

reflect パッケージと DeepEqual 関数

reflect パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、型情報、フィールド、メソッドなどを動的に操作できます。

reflect.DeepEqual 関数は、Go言語で2つの値が「深く」等しいかどうかを判断するために使用されます。これは、通常の == 演算子では比較できない複雑なデータ構造(スライス、マップ、構造体など)の比較に特に役立ちます。

DeepEqual の主な特徴は以下の通りです。

  • 再帰的な比較: 構造体や配列、スライス、マップの要素を再帰的に比較します。
  • 型の一致: 比較される2つの値は同じ型である必要があります。型が異なる場合、DeepEqualfalse を返します。
  • ポインタのデリファレンス: ポインタが指す先の値を比較します。
  • 関数の比較: 関数は、両方が nil の場合にのみ等しいと見なされます。それ以外の場合は false です。
  • nilと空の区別: このコミットで明確にされたように、スライスに関してはnilスライスと空スライスを区別します。これは、内部的な表現(ポインタがnilか否か)が異なるためです。

技術的詳細

このコミットは、reflect.DeepEqual 関数の実装自体を変更するものではなく、そのドキュメンテーションを改善するものです。具体的には、src/pkg/reflect/deepequal.go ファイルの DeepEqual 関数のコメントに以下の1行が追加されました。

--- a/src/pkg/reflect/deepequal.go
+++ b/src/pkg/reflect/deepequal.go
@@ -125,6 +125,7 @@ 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, maps, and fields of structs. It correctly
 // handles recursive types. Functions are equal only if they are both nil.
+// Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice.
 func DeepEqual(a1, a2 interface{}) bool {
  if a1 == nil || a2 == nil {
  return a1 == a2

この変更は、DeepEqual の既存の挙動(nilスライスと空スライスを区別する)を明示的に文書化することで、ユーザーがこの関数の動作をより正確に理解できるようにすることを目的としています。

DeepEqual の内部実装では、スライスを比較する際に、その Data ポインタが nil であるかどうかをチェックします。nilスライスの場合、Data ポインタは nil ですが、空スライスの場合、Data ポインタは有効な(ただし長さ0の)基盤配列を指しています。このポインタの違いが、DeepEqual が両者を異なるものとして扱う理由です。

例えば、以下のコードを考えます。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var nilSlice []int
	emptySlice := []int{}
	emptySliceMake := make([]int, 0)

	fmt.Printf("nilSlice == nil: %t\n", nilSlice == nil)
	fmt.Printf("emptySlice == nil: %t\n", emptySlice == nil)
	fmt.Printf("emptySliceMake == nil: %t\n", emptySliceMake == nil)

	fmt.Printf("reflect.DeepEqual(nilSlice, emptySlice): %t\n", reflect.DeepEqual(nilSlice, emptySlice))
	fmt.Printf("reflect.DeepEqual(nilSlice, emptySliceMake): %t\n", reflect.DeepEqual(nilSlice, emptySliceMake))
	fmt.Printf("reflect.DeepEqual(emptySlice, emptySliceMake): %t\n", reflect.DeepEqual(emptySlice, emptySliceMake))
}

このコードを実行すると、reflect.DeepEqual(nilSlice, emptySlice) および reflect.DeepEqual(nilSlice, emptySliceMake)false を返します。これは、DeepEqual がnilスライスと空スライスを異なるものとして扱っていることを示しています。このコミットは、この重要な挙動をドキュメントに明記することで、開発者が予期せぬ結果に遭遇するのを防ぎます。

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

変更は src/pkg/reflect/deepequal.go ファイルの126行目に行われました。

--- a/src/pkg/reflect/deepequal.go
+++ b/src/pkg/reflect/deepequal.go
@@ -125,6 +125,7 @@ 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, maps, and fields of structs. It correctly
 // handles recursive types. Functions are equal only if they are both nil.
+// Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice.
 func DeepEqual(a1, a2 interface{}) bool {
  if a1 == nil || a2 == nil {
  return a1 == a2

具体的には、DeepEqual 関数のドキュメンテーションコメントに以下の行が追加されました。

// Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice.

コアとなるコードの解説

このコミットは、Go言語の reflect パッケージにある DeepEqual 関数のドキュメンテーションを更新するものです。DeepEqual 関数は、2つのGoの値を「深く」比較し、それらが等しいかどうかを判断します。この「深さ」とは、単に値が同じであるかだけでなく、構造体、配列、スライス、マップなどの複合型の内部要素も再帰的に比較することを意味します。

追加されたコメント // Note: unlike regular ==, DeepEqual distinguishes an empty slice from a nil slice. は、DeepEqual の重要な挙動を明確にしています。

  • regular == との比較: Go言語では、スライス自体を == 演算子で直接比較することはできません(コンパイルエラーになります)。しかし、このコメントは、他の型(例えば整数や文字列)の == 比較とは異なり、DeepEqual がスライスの nil と空を区別するという点を強調しています。これは、nil スライスと空スライスが、要素を持たないという点では同じように見えても、内部的な表現(基盤となる配列へのポインタが nil かどうか)が異なるためです。
  • empty slicenil slice の区別:
    • nil slice: var s []T のように宣言されたスライスは nil スライスです。このスライスは基盤となる配列を持たず、そのポインタは nil です。
    • empty slice: []T{}make([]T, 0) のように初期化されたスライスは空スライスです。このスライスは基盤となる配列を持ちますが、その長さは0です。ポインタは nil ではありません。

DeepEqual は、これらの内部的な違いを認識し、nil スライスと空スライスを異なるものとして扱います。このドキュメントの追加により、開発者は DeepEqual を使用する際に、この特定の挙動を意識し、予期せぬ結果を避けることができます。これは、Go言語の設計思想である「明示的であること」と「驚きを最小限に抑えること」に沿った改善と言えます。

関連リンク

参考にした情報源リンク