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

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

  1. 配列変数の定義変更:
    • 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} に変更。
  2. スライス変数の追加:
    • var slice = array[:]var islice = iarray[:] を追加。これにより、既存の配列からスライスを作成し、スライス固有のテストケースを追加できるようになります。
  3. テストケースの追加:
    • fmttests変数に、新しく定義されたsliceisliceに対する%vフォーマットのテストケースが追加されています。これにより、スライスが正しく整形されることを確認します。
    • %#vフォーマット(Go構文)で配列とスライスが正しく表示されることを確認するためのテストケースが追加されています。特に、配列が[N]Type{...}の形式で出力されることを検証しています。

これらのテストケースの追加により、fmtパッケージが配列とスライスの両方を正しく処理し、特にGo構文での出力が期待通りになることが保証されます。

src/pkg/fmt/print.goの変更

このファイルでは、BigSwitchというラベルが付いた箇所にあるif文の条件が変更されています。

  • 変更前:

    if f.IsNil() {
        p.buf.WriteString("(nil)")
        break
    }
    

    このコードは、fnilである場合に"(nil)"という文字列をバッファに書き込み、処理を中断します。しかし、fが配列型の場合、f.IsNil()はパニックを引き起こす可能性があります。

  • 変更後:

    if f.Kind() == reflect.Slice && f.IsNil() {
        p.buf.WriteString("(nil)")
        break
    }
    

    この変更により、f.IsNil()が呼び出されるのは、freflect.Slice型であり、かつfnilである場合に限定されます。f.Kind() == reflect.Sliceという条件が追加されたことで、配列型に対してIsNil()が誤って呼び出されることがなくなり、実行時パニックが回避されます。これは、Goの型システムにおける配列とスライスの違いを適切に考慮した、より堅牢な実装です。

関連リンク

参考にした情報源リンク