[インデックス 10492] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおいて、Go構文で配列を整形して出力する際に、nil
チェックのロジックを修正し、テストスイートに配列の値を追加するものです。具体的には、reflect.Value.IsNil()
の呼び出しが、スライスに対してのみ行われるように変更され、配列に対しては不要なnil
チェックが行われないように改善されています。これにより、fmt
パッケージの動作がより正確になり、Goの型システムにおける配列とスライスの違いが適切に扱われるようになります。
コミット
commit 8362ee99b046bdbc19d6e8a806c656295ba56b2a
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Nov 23 09:04:02 2011 -0800
fmt: don't check for nil when printing arrays as Go syntax.
Also add array values to printing test suite.
Fixes #2468.
R=golang-dev, r
CC=golang-dev, remy
https://golang.org/cl/5436053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8362ee99b046bdbc19d6e8a806c656295ba56b2a
元コミット内容
commit 8362ee99b046bdbc19d6e8a806c656295ba56b2a
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Nov 23 09:04:02 2011 -0800
fmt: don't check for nil when printing arrays as Go syntax.
Also add array values to printing test suite.
Fixes #2468.
R=golang-dev, r
CC=golang-dev, remy
https://golang.org/cl/5436053
---
src/pkg/fmt/fmt_test.go | 16 ++++++++++++++--
src/pkg/fmt/print.go | 2 +-\n 2 files changed, 15 insertions(+), 3 deletions(-)\n
diff --git a/src/pkg/fmt/fmt_test.go b/src/pkg/fmt/fmt_test.go
index 6370560d0b..00aac798cb 100644
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -47,8 +47,10 @@ func TestFmtInterface(t *testing.T) {
const b32 uint32 = 1<<32 - 1
const b64 uint64 = 1<<64 - 1
-var array = []int{1, 2, 3, 4, 5}\n-var iarray = []interface{}{1, \"hello\", 2.5, nil}\n+var array = [5]int{1, 2, 3, 4, 5}\n+var iarray = [4]interface{}{1, \"hello\", 2.5, nil}\n+var slice = array[:]\n+var islice = iarray[:]\n
type A struct {
i int
@@ -327,6 +329,12 @@ var fmttests = []struct {
{\"%v\", &array, \"&[1 2 3 4 5]\"},\n {\"%v\", &iarray, \"&[1 hello 2.5 <nil>]\"},\n
+\t// slices\n+\t{\"%v\", slice, \"[1 2 3 4 5]\"},\n+\t{\"%v\", islice, \"[1 hello 2.5 <nil>]\"},\n+\t{\"%v\", &slice, \"&[1 2 3 4 5]\"},\n+\t{\"%v\", &islice, \"&[1 hello 2.5 <nil>]\"},\n+\n // complexes with %v\n {\"%v\", 1 + 2i, \"(1+2i)\"},\n {\"%v\", complex64(1 + 2i), \"(1+2i)\"},\
@@ -359,6 +367,10 @@ var fmttests = []struct {
{\"%#v\", SI{}, `fmt_test.SI{I:interface {}(nil)}`},\n {\"%#v\", []int(nil), `[]int(nil)`},\n {\"%#v\", []int{}, `[]int{}`},\n+\t{\"%#v\", array, `[5]int{1, 2, 3, 4, 5}`},\n+\t{\"%#v\", &array, `&[5]int{1, 2, 3, 4, 5}`},\n+\t{\"%#v\", iarray, `[4]interface {}{1, \"hello\", 2.5, interface {}(nil)}`},\n+\t{\"%#v\", &iarray, `&[4]interface {}{1, \"hello\", 2.5, interface {}(nil)}`},\n {\"%#v\", map[int]byte(nil), `map[int] uint8(nil)`},\n {\"%#v\", map[int]byte{}, `map[int] uint8{}`},\n
diff --git a/src/pkg/fmt/print.go b/src/pkg/fmt/print.go
index 7143e07a36..e5ca117240 100644
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -877,7 +877,7 @@ BigSwitch:
}\n if goSyntax {\n p.buf.WriteString(value.Type().String())\n-\t\t\tif f.IsNil() {\n+\t\t\tif f.Kind() == reflect.Slice && f.IsNil() {\n \t\t\t\tp.buf.WriteString(\"(nil)\")\n \t\t\t\tbreak\n \t\t\t}\n```
## 変更の背景
この変更は、Go言語の`fmt`パッケージにおけるバグ修正(Issue 2468)に対応するものです。元の実装では、Go構文(`%#v`フォーマット指定子など)で値を整形して出力する際に、`reflect.Value.IsNil()`メソッドが配列に対しても無条件に呼び出されていました。しかし、Goにおいて配列は値型であり、`nil`にはなり得ません。`IsNil()`メソッドは、ポインタ、インターフェース、チャネル、関数、マップ、スライスに対してのみ意味を持ち、これらの型が`nil`であるかどうかを判定します。配列に対して`IsNil()`を呼び出すと、パニック(実行時エラー)を引き起こす可能性がありました。
このコミットは、この問題を解決するために、`IsNil()`の呼び出しを`reflect.Slice`型の場合に限定することで、配列に対する不適切な`nil`チェックを排除し、プログラムの堅牢性を向上させています。また、配列とスライスの両方に対する`fmt`パッケージのテストカバレッジを向上させるために、新しいテストケースが追加されています。
## 前提知識の解説
### Go言語の`fmt`パッケージ
`fmt`パッケージは、Go言語におけるフォーマットI/O(入出力)を扱うための標準パッケージです。C言語の`printf`/`scanf`に似た機能を提供し、様々なデータ型を文字列に変換したり、文字列からデータを解析したりするために使用されます。`fmt.Printf`や`fmt.Sprintf`などの関数を通じて、`%v`(デフォルトフォーマット)、`%#v`(Go構文での表現)、`%T`(型の表示)などのフォーマット指定子を使って、値の表示形式を制御できます。
### Go言語の配列とスライス
Go言語には、配列(Array)とスライス(Slice)という2つの異なるデータ構造があります。
* **配列 (Array)**:
* 固定長で、宣言時に要素数が決定されます。
* 値型であり、配列全体がコピーされます。
* `[N]T`のように宣言され、`N`は要素数、`T`は要素の型です。
* 例: `var a [5]int` は5つの整数を格納できる配列です。
* 配列は`nil`にはなり得ません。常にメモリが割り当てられ、ゼロ値で初期化されます。
* **スライス (Slice)**:
* 可変長で、実行時に要素数を変更できます。
* 配列の一部を参照するデータ構造であり、内部的にはポインタ、長さ、容量を持ちます。
* 参照型であり、スライスをコピーしても同じ基底配列を参照します。
* `[]T`のように宣言され、`T`は要素の型です。
* 例: `var s []int` は整数のスライスです。
* スライスは`nil`になることができ、その場合、基底配列を参照せず、長さと容量が0になります。
### Go言語の`reflect`パッケージ
`reflect`パッケージは、Go言語の実行時リフレクション機能を提供します。これにより、プログラムは自身の構造(型、値、メソッドなど)を検査し、動的に操作することができます。
* **`reflect.Value`**: Goの任意の値を抽象化したものです。この型を通じて、値の型情報(`Type()`)、種類(`Kind()`)、そして特定の操作(例: `IsNil()`)を行うことができます。
* **`reflect.Value.Kind()`**: `reflect.Value`が表す値の基本的な種類(例: `reflect.Int`, `reflect.String`, `reflect.Slice`, `reflect.Array`など)を返します。
* **`reflect.Value.IsNil()`**: `reflect.Value`が表す値が`nil`であるかどうかを判定します。このメソッドは、ポインタ、インターフェース、チャネル、関数、マップ、スライスに対してのみ有効です。これらの型以外に対して呼び出すとパニックを引き起こします。
### `nil`について
Go言語における`nil`は、特定の型のゼロ値であり、「値がない」ことを示します。`nil`は、ポインタ、スライス、マップ、チャネル、関数、インターフェース型にのみ適用されます。配列は値型であるため、`nil`にはなりません。
## 技術的詳細
このコミットの核心は、`src/pkg/fmt/print.go`ファイル内の`BigSwitch`というラベルが付いたセクションの変更です。このセクションは、`fmt`パッケージがGoの値を文字列に整形する際の主要なロジックを含んでいます。
変更前は、Go構文(`goSyntax`が`true`の場合)で値を整形する際に、`f.IsNil()`という呼び出しが`reflect.Value`オブジェクト`f`に対して無条件に行われていました。ここで`f`は整形対象の値を表す`reflect.Value`です。
```go
// 変更前
if goSyntax {
p.buf.WriteString(value.Type().String())
if f.IsNil() { // ここで配列に対してIsNil()が呼ばれる可能性があった
p.buf.WriteString("(nil)")
break
}
// ...
}
配列はnil
になり得ないため、もしf
が配列を表すreflect.Value
であった場合、f.IsNil()
の呼び出しは実行時パニックを引き起こす可能性がありました。
このコミットでは、この問題を解決するために、f.IsNil()
の呼び出しの前にf.Kind() == reflect.Slice
という条件を追加しています。
// 変更後
if goSyntax {
p.buf.WriteString(value.Type().String())
if f.Kind() == reflect.Slice && f.IsNil() { // スライス型の場合のみIsNil()を呼び出す
p.buf.WriteString("(nil)")
break
}
// ...
}
この変更により、IsNil()
メソッドは、その呼び出しが安全かつ意味を持つreflect.Slice
型の場合にのみ実行されるようになります。配列型の場合、f.Kind() == reflect.Slice
の条件がfalse
となるため、f.IsNil()
は呼び出されず、パニックが回避されます。
また、src/pkg/fmt/fmt_test.go
には、配列とスライスの両方に対する新しいテストケースが追加されています。これにより、fmt
パッケージがこれらの型を正しく整形できることが保証されます。特に、%#v
フォーマット指定子を使った配列のGo構文表現がテストされています。
コアとなるコードの変更箇所
src/pkg/fmt/fmt_test.go
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -47,8 +47,10 @@ func TestFmtInterface(t *testing.T) {
const b32 uint32 = 1<<32 - 1
const b64 uint64 = 1<<64 - 1
-var array = []int{1, 2, 3, 4, 5}
-var iarray = []interface{}{1, "hello", 2.5, nil}
+var array = [5]int{1, 2, 3, 4, 5}
+var iarray = [4]interface{}{1, "hello", 2.5, nil}
+var slice = array[:]
+var islice = iarray[:]
type A struct {
i int
@@ -327,6 +329,12 @@ var fmttests = []struct {
{"%v", &array, "&[1 2 3 4 5]"},
{"%v", &iarray, "&[1 hello 2.5 <nil>]"},
+ // slices
+ {"%v", slice, "[1 2 3 4 5]"},
+ {"%v", islice, "[1 hello 2.5 <nil>]"},
+ {"%v", &slice, "&[1 2 3 4 5]"},
+ {"%v", &islice, "&[1 hello 2.5 <nil>]"},
+
// complexes with %v
{"%v", 1 + 2i, "(1+2i)"},
{"%v", complex64(1 + 2i), "(1+2i)"},
@@ -359,6 +367,10 @@ var fmttests = []struct {
{"%#v", SI{}, `fmt_test.SI{I:interface {}(nil)}`},
{"%#v", []int(nil), `[]int(nil)`},
{"%#v", []int{}, `[]int{}`},
+ {"%#v", array, `[5]int{1, 2, 3, 4, 5}`},
+ {"%#v", &array, `&[5]int{1, 2, 3, 4, 5}`},
+ {"%#v", iarray, `[4]interface {}{1, "hello", 2.5, interface {}(nil)}`},
+ {"%#v", &iarray, `&[4]interface {}{1, "hello", 2.5, interface {}(nil)}`},
{"%#v", map[int]byte(nil), `map[int] uint8(nil)`},
{"%#v", map[int]byte{}, `map[int] uint8{}`},
src/pkg/fmt/print.go
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -877,7 +877,7 @@ BigSwitch:
}
if goSyntax {
p.buf.WriteString(value.Type().String())
- if f.IsNil() {
+ if f.Kind() == reflect.Slice && f.IsNil() {
p.buf.WriteString("(nil)")
break
}
コアとなるコードの解説
src/pkg/fmt/fmt_test.go
の変更
- 配列変数の定義変更:
var array = []int{1, 2, 3, 4, 5}
をvar array = [5]int{1, 2, 3, 4, 5}
に変更。これにより、array
がスライスではなく、固定長の配列として明示的に定義されます。- 同様に、
var iarray = []interface{}{1, "hello", 2.5, nil}
をvar iarray = [4]interface{}{1, "hello", 2.5, nil}
に変更。
- スライス変数の追加:
var slice = array[:]
とvar islice = iarray[:]
を追加。これにより、既存の配列からスライスを作成し、スライス固有のテストケースを追加できるようになります。
- テストケースの追加:
fmttests
変数に、新しく定義されたslice
とislice
に対する%v
フォーマットのテストケースが追加されています。これにより、スライスが正しく整形されることを確認します。%#v
フォーマット(Go構文)で配列とスライスが正しく表示されることを確認するためのテストケースが追加されています。特に、配列が[N]Type{...}
の形式で出力されることを検証しています。
これらのテストケースの追加により、fmt
パッケージが配列とスライスの両方を正しく処理し、特にGo構文での出力が期待通りになることが保証されます。
src/pkg/fmt/print.go
の変更
このファイルでは、BigSwitch
というラベルが付いた箇所にあるif
文の条件が変更されています。
-
変更前:
if f.IsNil() { p.buf.WriteString("(nil)") break }
このコードは、
f
がnil
である場合に"(nil)"
という文字列をバッファに書き込み、処理を中断します。しかし、f
が配列型の場合、f.IsNil()
はパニックを引き起こす可能性があります。 -
変更後:
if f.Kind() == reflect.Slice && f.IsNil() { p.buf.WriteString("(nil)") break }
この変更により、
f.IsNil()
が呼び出されるのは、f
がreflect.Slice
型であり、かつf
がnil
である場合に限定されます。f.Kind() == reflect.Slice
という条件が追加されたことで、配列型に対してIsNil()
が誤って呼び出されることがなくなり、実行時パニックが回避されます。これは、Goの型システムにおける配列とスライスの違いを適切に考慮した、より堅牢な実装です。
関連リンク
- Go CL 5436053: https://golang.org/cl/5436053
- Go Issue 2468: https://golang.org/issue/2468
参考にした情報源リンク
- Go言語の公式ドキュメント:
fmt
パッケージ: https://pkg.go.dev/fmtreflect
パッケージ: https://pkg.go.dev/reflect
- Go言語における配列とスライス: https://go.dev/blog/go-slices-usage-and-internals (Go Slices: usage and internals)
- Go言語における
nil
: https://go.dev/blog/nil (The Go Blog: Nil) - Go言語のIssue Tracker: https://go.dev/issue
- GitHub: https://github.com/golang/go