[インデックス 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