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

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

このコミットは、Go言語の標準ライブラリであるfmtパッケージにおける、nil値のフォーマット時の幅指定に関するバグ修正です。具体的には、fmt.Printf関数でnil値をフォーマットする際に、幅指定(例: %10v%-10T)が正しく適用されない問題を解決します。

コミット

commit cbd2c7a283b785f109dd350ef4d20dd9871d3359
Author: Robert Dinu <r@oktett.se>
Date:   Wed Feb 20 14:30:15 2013 -0800

    fmt: fix width for nil values
    
    Apply width when using Printf with nil values.
    Fixes #4772.
    
    R=r, adg
    CC=golang-dev
    https://golang.org/cl/7314114

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

https://github.com/golang/go/commit/cbd2c7a283b785f109dd350ef4d20dd9871d3359

元コミット内容

fmt: fix width for nil values

Apply width when using Printf with nil values.
Fixes #4772.

R=r, adg
CC=golang-dev
https://golang.org/cl/7314114

変更の背景

Go言語のfmtパッケージは、C言語のprintfに似た書式指定文字列を用いた出力機能を提供します。この機能には、出力する値の幅を指定する機能(例: %10vで10文字分の幅を確保し、右寄せで出力)が含まれています。

このコミットが修正する問題は、fmt.Printfnil値をフォーマットする際に、この幅指定が無視されてしまうというものでした。例えば、fmt.Printf("%10v", nil)と記述した場合、期待される出力は <nil>(5文字の<nil>の前に5つのスペース)ですが、バグのあるバージョンでは単に<nil>と出力されてしまい、幅指定が適用されませんでした。これは、nil値の特殊な扱いが原因で、通常のパディング処理がスキップされていたためと考えられます。

この問題は、GoのIssue #4772として報告されていました。

前提知識の解説

Go言語のfmtパッケージ

fmtパッケージは、Go言語におけるフォーマットI/Oを実装するためのパッケージです。主に以下の関数がよく使われます。

  • fmt.Print: 引数をデフォルトのフォーマットで出力します。
  • fmt.Println: 引数をデフォルトのフォーマットで出力し、改行を追加します。
  • fmt.Printf: 書式指定文字列と引数を用いてフォーマットされた文字列を出力します。

fmt.Printfの書式指定

fmt.Printfでは、C言語のprintfと同様に、様々な書式指定子(verb)を使用して出力形式を制御できます。

  • %v: 値のデフォルトフォーマット。構造体や配列など、あらゆる型に対応します。
  • %T: 値の型名を出力します。
  • 幅指定: %[幅]vのように、数値で幅を指定できます。例えば、%10vは10文字分の幅を確保し、デフォルトでは右寄せで出力します。
  • フラグ: -フラグは左寄せを指定します。例えば、%-10vは10文字分の幅を確保し、左寄せで出力します。

nil

Go言語におけるnilは、ポインタ、インターフェース、マップ、スライス、チャネル、関数などのゼロ値を表す特別な識別子です。これらの型の変数が初期化されていない状態や、有効な値を指していない状態を示します。nilは特定の型に属するものではなく、これらの型の「値がない」状態を表現します。

技術的詳細

この修正は、fmtパッケージ内部のpp(printer)構造体のprintFieldメソッドに焦点を当てています。printFieldメソッドは、fmt.Printfが個々の引数をフォーマットする際に呼び出される内部関数です。

修正前のコードでは、fieldnilである場合に、verb'T'または'v'であれば、p.buf.Write(nilAngleBytes)を直接呼び出していました。ここでnilAngleBytesは文字列<nil>を表すバイトスライスです。このWriteメソッドは、単にバッファにバイトを書き込むだけであり、幅指定やパディングのロジックは含まれていませんでした。

一方、通常の(nilではない)値のフォーマットでは、p.fmt.padメソッドが使用されます。このpadメソッドは、指定された幅に基づいてパディング(スペースの追加)を行う責任を負っています。

このコミットの目的は、nil値のフォーマットにおいても、通常の値と同様に幅指定が適用されるようにすることです。

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

変更はsrc/pkg/fmt/print.goファイルにあります。

--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -729,7 +729,7 @@ func (p *pp) printField(field interface{}, verb rune, plus, goSyntax bool, depth
 
  if field == nil {
  	if verb == 'T' || verb == 'v' {
- 		p.buf.Write(nilAngleBytes)
+ 		p.fmt.pad(nilAngleBytes)
  	} else {
  		p.badVerb(verb)
  	}

また、この変更を検証するためのテストケースがsrc/pkg/fmt/fmt_test.goに追加されています。

--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -178,6 +178,8 @@ var fmttests = []struct {
  {"%.3q", "日本語日本語", `"日本語"`},
  {"%.3q", []byte("日本語日本語"), `"日本語"`},
  {"%10.1q", "日本語日本語", `       "日"`},
+ {"%10v", nil, "     <nil>"},
+ {"%-10v", nil, "<nil>     "},
 
  // integers
  {"%d", 12345, "12345"},
@@ -437,6 +439,8 @@ var fmttests = []struct {
  {"%T", renamedComplex128(4 - 3i), "fmt_test.renamedComplex128"},
  {"%T", intVal, "int"},
  {"%6T", &intVal, "  *int"},
+ {"%10T", nil, "     <nil>"},
+ {"%-10T", nil, "<nil>     "},
 
  // %p
  {"p0=%p", new(int), "p0=0xPTR"},

コアとなるコードの解説

src/pkg/fmt/print.goの変更は非常にシンプルですが、その影響は大きいです。

修正前:

p.buf.Write(nilAngleBytes)

この行は、nil値が%vまたは%Tでフォーマットされる際に、直接<nil>という文字列を内部バッファに書き込んでいました。この操作は、幅指定やパディングのロジックを完全にバイパスしていました。

修正後:

p.fmt.pad(nilAngleBytes)

この行では、p.buf.Writeの代わりにp.fmt.padが呼び出されています。p.fmtfmtパッケージのフォーマッタ(fmt構造体)への参照であり、padメソッドは、現在のフォーマット設定(幅、精度、フラグなど)に基づいて、与えられたバイトスライス(この場合は<nil>を表すnilAngleBytes)を適切にパディングして出力する役割を担っています。

この変更により、nil値がフォーマットされる際にも、他の値と同様にfmtパッケージのパディングロジックが適用されるようになり、幅指定が正しく機能するようになりました。

src/pkg/fmt/fmt_test.goに追加されたテストケースは、この修正が意図通りに機能することを確認します。

  • {"%10v", nil, " <nil>"}: %10vnilをフォーマットした場合、10文字幅で右寄せされ、<nil>の前に5つのスペースが追加されることを期待します。
  • {"%-10v", nil, "<nil> "}: %-10vnilをフォーマットした場合、10文字幅で左寄せされ、<nil>の後に5つのスペースが追加されることを期待します。
  • 同様に、%Tに対しても幅指定が適用されることを確認するテストケースが追加されています。

これらのテストケースは、修正が正しく機能していることを検証するための重要な役割を果たします。

関連リンク

参考にした情報源リンク

  • コミット情報: /home/orange/Project/comemo/commit_data/15335.txt
  • GitHub上のコミットページ: https://github.com/golang/go/commit/cbd2c7a283b785f109dd350ef4d20dd9871d3359
  • Go言語のfmtパッケージのソースコード(当時のバージョンに近いもの)
  • Go言語のfmtパッケージの書式指定に関する一般的な情報
  • Go言語におけるnilの概念に関する一般的な情報
  • Go issue #39886 (関連する可能性のある議論): https://github.com/golang/go/issues/39886 (ただし、直接のIssue #4772は見つからなかったため、一般的なfmtの挙動に関する議論として参照)