[インデックス 12030] ファイルの概要
このコミットは、Go言語の標準ライブラリbytesパッケージ内のCompare関数とEqual関数のセマンティクス、特にnil引数が渡された場合の挙動を明確にし、それに対応するテストを追加するものです。これにより、これらの関数の振る舞いがより予測可能で、ドキュメント化されたものとなります。
コミット
bytes: document Compare/Equal semantics for nil arguments, and add tests.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/85f2d18a726a999b446a16039aa4bef4e8a4e9e9
元コミット内容
commit 85f2d18a726a999b446a16039aa4bef4e8a4e9e9
Author: David Symonds <dsymonds@golang.org>
Date: Sat Feb 18 17:39:40 2012 +1100
bytes: document Compare/Equal semantics for nil arguments, and add tests.
R=golang-dev, bradfitz, r, r
CC=golang-dev
https://golang.org/cl/5676090
---
src/pkg/bytes/bytes.go | 2 ++\
src/pkg/bytes/bytes_test.go | 41 ++++++++++++++++++++++++-----------------
2 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/src/pkg/bytes/bytes.go b/src/pkg/bytes/bytes.go
index e94a0ec5c4..7d1426fb41 100644
--- a/src/pkg/bytes/bytes.go
+++ b/src/pkg/bytes/bytes.go
@@ -13,6 +13,7 @@ import (
// Compare returns an integer comparing the two byte arrays lexicographically.
// The result will be 0 if a==b, -1 if a < b, and +1 if a > b
+// A nil argument is equivalent to an empty slice.
func Compare(a, b []byte) int {
m := len(a)
if m > len(b) {
@@ -37,6 +38,7 @@ func Compare(a, b []byte) int {
// Equal returns a boolean reporting whether a == b.
+// A nil argument is equivalent to an empty slice.
func Equal(a, b []byte) bool
func equalPortable(a, b []byte) bool {
diff --git a/src/pkg/bytes/bytes_test.go b/src/pkg/bytes/bytes_test.go
index 2a1d41b910..000f235176 100644
--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -46,32 +46,39 @@ type BinOpTest struct {
i int
}
-var comparetests = []BinOpTest{
- {"", "", 0},
- {"a", "", 1},
- {"", "a", -1},
- {"abc", "abc", 0},
- {"ab", "abc", -1},
- {"abc", "ab", 1},
- {"x", "ab", 1},
- {"ab", "x", -1},
- {"x", "a", 1},
- {"b", "x", -1},
+var compareTests = []struct {
+ a, b []byte
+ i int
+}{
+ {[]byte(""), []byte(""), 0},
+ {[]byte("a"), []byte(""), 1},
+ {[]byte(""), []byte("a"), -1},
+ {[]byte("abc"), []byte("abc"), 0},
+ {[]byte("ab"), []byte("abc"), -1},
+ {[]byte("abc"), []byte("ab"), 1},
+ {[]byte("x"), []byte("ab"), 1},
+ {[]byte("ab"), []byte("x"), -1},
+ {[]byte("x"), []byte("a"), 1},
+ {[]byte("b"), []byte("x"), -1},
+ // nil tests
+ {nil, nil, 0},
+ {[]byte(""), nil, 0},
+ {nil, []byte(""), 0},
+ {[]byte("a"), nil, 1},
+ {nil, []byte("a"), -1},
}
func TestCompare(t *testing.T) {
- for _, tt := range comparetests {
- a := []byte(tt.a)
- b := []byte(tt.b)
- cmp := Compare(a, b)
+ for _, tt := range compareTests {
+ cmp := Compare(tt.a, tt.b)
if cmp != tt.i {
t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
}
- eql := Equal(a, b)
+ eql := Equal(tt.a, tt.b)
if eql != (tt.i == 0) {
t.Errorf(`Equal(%q, %q) = %v`, tt.a, tt.b, eql)
}
- eql = EqualPortable(a, b)
+ eql = EqualPortable(tt.a, tt.b)
if eql != (tt.i == 0) {
t.Errorf(`EqualPortable(%q, %q) = %v`, tt.a, tt.b, eql)
}
変更の背景
このコミットの主な背景は、Go言語のbytesパッケージにおけるCompare関数とEqual関数が、nilのバイトスライスを引数として受け取った場合の挙動を明確にすることにあります。Go言語では、nilスライスと空のスライス([]byte{})は異なる概念ですが、多くのGoの関数や操作では、これらを同じように扱うことが慣例となっています。
しかし、bytes.Compareやbytes.Equalのような比較関数において、nil引数のセマンティクスが明示的にドキュメント化されていない場合、開発者はその挙動について混乱したり、予期せぬ結果に遭遇したりする可能性があります。このコミットは、この曖昧さを解消し、nil引数が空のスライスと同等に扱われることを明示的にドキュメントに追加し、その挙動を検証するためのテストケースを拡充することで、ライブラリの堅牢性と使いやすさを向上させることを目的としています。
前提知識の解説
Go言語におけるnilスライスと空スライス
Go言語において、スライスは基盤となる配列への参照、長さ、容量を持つデータ構造です。
nilスライス:var s []byteのように宣言されたスライスは、初期値としてnilになります。nilスライスは基盤となる配列への参照を持たず、長さも容量も0です。nilスライスは有効なスライスであり、多くの操作(len(s)、cap(s)、append(s, ...)など)で問題なく使用できます。- 空スライス:
[]byte{}やmake([]byte, 0)のように作成されたスライスは、空スライスと呼ばれます。これらは基盤となる配列への参照を持つことがありますが、長さは0です。
Goの慣例として、多くの関数はnilスライスと空スライスを同じように扱います。例えば、json.Marshalはnilスライスと空スライスの両方を[]としてJSONにエンコードします。このコミットは、bytesパッケージの比較関数もこの慣例に従うことを明示するものです。
bytes.Compare関数
bytes.Compare(a, b []byte) intは、2つのバイトスライスaとbを辞書順に比較し、以下のいずれかの整数を返します。
0:aとbが等しい場合-1:aがbより小さい場合+1:aがbより大きい場合
「辞書順」とは、文字列の比較と同様に、バイト列の各要素を先頭から順に比較していく方法です。
bytes.Equal関数
bytes.Equal(a, b []byte) boolは、2つのバイトスライスaとbが等しいかどうかを報告するブール値を返します。これはbytes.Compareが0を返すことと同義です。
技術的詳細
このコミットは、主に以下の2つの側面で変更を加えています。
-
ドキュメントの追加:
src/pkg/bytes/bytes.goファイルにおいて、Compare関数とEqual関数のコメントに「A nil argument is equivalent to an empty slice.」(nil引数は空のスライスと同等である)という記述が追加されました。これにより、これらの関数にnilスライスが渡された場合の挙動が公式に明文化され、開発者が混乱する可能性が低減されます。 -
テストケースの拡充:
src/pkg/bytes/bytes_test.goファイルにおいて、compareTestsというテストデータ構造が変更され、nilスライスを引数として含む新しいテストケースが追加されました。- 以前は
BinOpTestという構造体を使用し、文字列リテラルを[]byteに変換してテストしていましたが、新しいcompareTestsは直接[]byte型のフィールドを持つ匿名構造体のスライスとして定義されています。これにより、nilスライスを直接テストデータとして指定できるようになりました。 - 追加された
nil関連のテストケースは以下の通りです。{nil, nil, 0}: 両方nilの場合、等しい(0を返す)。{[]byte(""), nil, 0}: 空スライスとnilの場合、等しい(0を返す)。{nil, []byte(""), 0}:nilと空スライスの場合、等しい(0を返す)。{[]byte("a"), nil, 1}: 非空スライスとnilの場合、非空スライスの方が大きい(1を返す)。{nil, []byte("a"), -1}:nilと非空スライスの場合、nilの方が小さい(-1を返す)。
- 以前は
これらのテストケースの追加により、CompareおよびEqual関数がnil引数を空スライスとして正しく処理し、期待される結果を返すことが保証されます。また、テストループ内でa := []byte(tt.a)やb := []byte(tt.b)といった文字列からバイトスライスへの変換が不要になり、テストコードがより直接的になりました。
コアとなるコードの変更箇所
src/pkg/bytes/bytes.go
--- a/src/pkg/bytes/bytes.go
+++ b/src/pkg/bytes/bytes.go
@@ -13,6 +13,7 @@ import (
// Compare returns an integer comparing the two byte arrays lexicographically.
// The result will be 0 if a==b, -1 if a < b, and +1 if a > b
+// A nil argument is equivalent to an empty slice.
func Compare(a, b []byte) int {
m := len(a)
if m > len(b) {
@@ -37,6 +38,7 @@ func Compare(a, b []byte) int {
// Equal returns a boolean reporting whether a == b.
+// A nil argument is equivalent to an empty slice.
func Equal(a, b []byte) bool
func equalPortable(a, b []byte) bool {
src/pkg/bytes/bytes_test.go
--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -46,32 +46,39 @@ type BinOpTest struct {
i int
}
-var comparetests = []BinOpTest{
- {"", "", 0},
- {"a", "", 1},
- {"", "a", -1},
- {"abc", "abc", 0},
- {"ab", "abc", -1},
- {"abc", "ab", 1},
- {"x", "ab", 1},
- {"ab", "x", -1},
- {"x", "a", 1},
- {"b", "x", -1},
+var compareTests = []struct {
+ a, b []byte
+ i int
+}{
+ {[]byte(""), []byte(""), 0},
+ {[]byte("a"), []byte(""), 1},
+ {[]byte(""), []byte("a"), -1},
+ {[]byte("abc"), []byte("abc"), 0},
+ {[]byte("ab"), []byte("abc"), -1},
+ {[]byte("abc"), []byte("ab"), 1},
+ {[]byte("x"), []byte("ab"), 1},
+ {[]byte("ab"), []byte("x"), -1},
+ {[]byte("x"), []byte("a"), 1},
+ {[]byte("b"), []byte("x"), -1},
+ // nil tests
+ {nil, nil, 0},
+ {[]byte(""), nil, 0},
+ {nil, []byte(""), 0},
+ {[]byte("a"), nil, 1},
+ {nil, []byte("a"), -1},
}
func TestCompare(t *testing.T) {
- for _, tt := range comparetests {
- a := []byte(tt.a)
- b := []byte(tt.b)
- cmp := Compare(a, b)
+ for _, tt := range compareTests {
+ cmp := Compare(tt.a, tt.b)
if cmp != tt.i {
t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
}
- eql := Equal(a, b)
+ eql := Equal(tt.a, tt.b)
if eql != (tt.i == 0) {
t.Errorf(`Equal(%q, %q) = %v`, tt.a, tt.b, eql)
}
- eql = EqualPortable(a, b)
+ eql = EqualPortable(tt.a, tt.b)
if eql != (tt.i == 0) {
t.Errorf(`EqualPortable(%q, %q) = %v`, tt.a, tt.b, eql)
}
コアとなるコードの解説
src/pkg/bytes/bytes.goの変更
Compare関数とEqual関数のドキュメントコメントに、A nil argument is equivalent to an empty slice.という一文が追加されました。これは、これらの関数がnilバイトスライスを空のバイトスライス(長さ0)として扱うことを明示しています。この変更自体は関数のロジックには影響を与えませんが、APIの振る舞いを明確にし、開発者がnilスライスを安全に渡せることを保証します。Go言語の設計哲学では、ドキュメントによる明確化が非常に重要視されます。
src/pkg/bytes/bytes_test.goの変更
-
テストデータ構造の変更:
- 以前は
BinOpTestというカスタム構造体を使用していましたが、新しいcompareTestsは匿名構造体のスライスとして定義されています。これにより、テストケースの定義がより簡潔になり、特に[]byte型を直接フィールドに持つことで、nilスライスをテストデータとして直接指定できるようになりました。 - 古い定義では文字列リテラルを
[]byteに変換していましたが、新しい定義では直接[]byte("")のようにバイトスライスリテラルを使用しています。
- 以前は
-
nilテストケースの追加:// nil testsというコメントの下に、nilスライスを含む5つの新しいテストケースが追加されました。これらのテストは、nilスライスが空スライスと同等に扱われるという新しいドキュメントの記述を検証します。- 具体的には、
nilとnil、nilと空スライス、空スライスとnilが等しいと判断されること、そして非空スライスとnilの比較が期待通りに機能することを確認します。
-
テストループの簡素化:
TestCompare関数内のループで、以前はa := []byte(tt.a)やb := []byte(tt.b)のように文字列からバイトスライスへの変換を行っていましたが、新しいテストデータ構造ではtt.aとtt.bが既に[]byte型であるため、これらの変換が不要になりました。これにより、テストコードがより直接的で効率的になっています。
これらの変更は、bytesパッケージの堅牢性を高め、nilスライスを扱う際の予期せぬ挙動を防ぎ、開発者にとってより信頼性の高いAPIを提供することに貢献しています。
関連リンク
- Go CL 5676090: https://golang.org/cl/5676090
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- GoDoc bytes package: https://pkg.go.dev/bytes
- Go言語のnilと空スライスについて: (一般的なGo言語のドキュメントやブログ記事を参照)
- 例: https://qiita.com/tetsuzawa/items/11111111111111111111 (Qiita記事は参考例であり、特定の記事を指すものではありません)
- 例: https://zenn.dev/link/comments/11111111111111111111 (Zenn記事は参考例であり、特定の記事を指すものではありません)
- Go言語の公式ドキュメントやEffective Goも参照されるべき情報源です。
- Effective Go: https://go.dev/doc/effective_go#slices
- The Go Programming Language Specification - Slice types: https://go.dev/ref/spec#Slice_types
- The Go Programming Language Specification - Comparison operators: https://go.dev/ref/spec#Comparison_operators
- The Go Programming Language Specification - Package bytes: https://go.dev/ref/spec#Package_bytes