[インデックス 1331] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおける書式設定機能の改善とバグ修正に関するものです。具体的には、構造体のメンバーを出力する際にフィールド名を表示する機能の追加、カスタムフォーマッタが書式設定フラグにアクセスできるようにする機能の拡張、そして構造体メンバーの文字列が意図せず連結されてしまう問題の修正が含まれています。
コミット
commit bf67afc84e456e03e5ca14e2e4039497b36051ec
Author: Russ Cox <rsc@golang.org>
Date: Thu Dec 11 16:53:33 2008 -0800
print field names on struct members.
also don't concatenate strings next
to each other in the struct,
like p.doprint does.
expose additional print flags to formatters
R=r
DELTA=128 (111 added, 11 deleted, 6 changed)
OCL=20991
CL=21018
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bf67afc84e456e03e5ca14e2e4039497b36051ec
元コミット内容
このコミットは、以下の主要な変更を含んでいます。
- 構造体メンバーのフィールド名表示:
fmt
パッケージで構造体を出力する際に、フィールド名も一緒に表示する機能が追加されました。これは特にデバッグ時や構造体の内容を詳細に確認したい場合に有用です。 - フォーマッタへの追加フラグ公開: カスタムフォーマッタが、書式設定文字列で指定されたフラグ(例:
-
,+
,#
,0
)の状態を問い合わせるための新しいメソッドがfmt.Formatter
インターフェースに追加されました。これにより、カスタムフォーマッタはより柔軟な出力制御が可能になります。 - 構造体内の文字列連結問題の修正: 構造体のメンバーをプリントする際に、隣接する文字列フィールドが意図せず連結されてしまうバグが修正されました。
変更の背景
Go言語のfmt
パッケージは、C言語のprintf
に似た書式設定機能を提供しますが、Goの型システムとリフレクションを活用してより強力な機能を実現しています。このコミットが行われた2008年12月は、Go言語がまだ初期開発段階にあった時期であり、fmt
パッケージも継続的に機能改善とバグ修正が行われていました。
このコミットの背景には、以下のニーズがあったと考えられます。
- デバッグの利便性向上: 構造体の内容を
%v
動詞で出力する際、単に値が並ぶだけではどの値がどのフィールドに対応するのか分かりにくい場合があります。フィールド名が表示されることで、構造体の内容をより直感的に理解できるようになります。これは、特に複雑な構造体やネストされた構造体を扱う際に重要です。 - カスタムフォーマッタの表現力向上:
fmt.Formatter
インターフェースを実装することで、任意の型が独自の書式設定ロジックを提供できます。しかし、既存のインターフェースでは、書式設定文字列で指定されたフラグ(例:%-v
,%+v
)の状態を直接取得する方法がありませんでした。この制限により、カスタムフォーマッタがフラグに応じた異なる出力を行うことが困難でした。フラグへのアクセスを可能にすることで、カスタムフォーマッタの表現力と柔軟性が大幅に向上します。 - 出力の正確性の確保: 構造体メンバーの出力時に文字列が意図せず連結されるバグは、出力の正確性を損なうものであり、修正が必要でした。これは、
fmt
パッケージが提供する出力が常に期待通りであることを保証するために不可欠です。
これらの改善は、Go言語のfmt
パッケージをより堅牢で使いやすくするための重要なステップでした。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念に関する前提知識が必要です。
-
fmt
パッケージ:- Go言語の標準ライブラリで、書式設定されたI/O(入出力)を提供します。
fmt.Printf
,fmt.Sprintf
,fmt.Println
などの関数が含まれます。 - 書式設定動詞 (Verbs):
%v
(デフォルトの書式設定),%d
(整数),%s
(文字列) など、様々な型に対応する動詞があります。 - 書式設定フラグ (Flags): 動詞の前に置かれる修飾子で、出力の挙動を変更します。例えば、
+
フラグは数値の符号を表示したり、構造体のフィールド名を表示したりするために使われます。-
フラグは左寄せ、#
フラグは代替書式(例: 0xプレフィックス)などに使われます。 - 幅 (Width) と 精度 (Precision):
%10s
のように幅を指定したり、%.2f
のように浮動小数点数の精度を指定したりできます。
- Go言語の標準ライブラリで、書式設定されたI/O(入出力)を提供します。
-
reflect
パッケージ:- Go言語のランタイムリフレクション機能を提供します。これにより、プログラムは実行時に自身の構造を検査し、操作することができます。
reflect.Value
: 任意のGoの値を表す型です。reflect.Type
: 任意のGoの型を表す型です。reflect.StructKind
:reflect.Type
が構造体であることを示す定数です。reflect.StructValue
: 構造体の値を表すreflect.Value
の具体的な型です。reflect.StructType
: 構造体の型情報を表すreflect.Type
の具体的な型です。Type().Field(i)
: 構造体のi
番目のフィールドの型情報を取得します。Len()
: スライス、配列、マップ、構造体などの長さ(要素数やフィールド数)を取得します。Get()
: インターフェースの値を取得します。
-
インターフェース (
interface
):- Go言語における抽象型であり、メソッドのシグネチャの集合を定義します。
fmt.Formatter
インターフェース:Format(f Formatter, c int)
メソッドを持つインターフェースです。このインターフェースを実装する型は、fmt
パッケージの書式設定関数によって独自の出力ロジックを提供できます。f
はfmt.Formatter
自身であり、c
は書式設定動詞の文字コードです。
-
io
パッケージ:- 基本的なI/Oプリミティブを提供します。
io.WriteString(w Writer, s string)
:w
に文字列s
を書き込みます。
これらの概念を理解することで、コミットがfmt
パッケージの内部でどのようにリフレクションとインターフェースを活用して書式設定機能を拡張・改善しているかを深く把握できます。
技術的詳細
このコミットは、fmt
パッケージの内部実装にいくつかの重要な変更を加えています。
-
fmt.Formatter
インターフェースの拡張:src/lib/fmt/print.go
において、Formatter
インターフェースに新しいメソッドFlag(int) bool
が追加されました。- このメソッドは、書式設定動詞に適用された特定のフラグ(例:
'-'
,'+'
,'#'
,' '
,'0'
)が設定されているかどうかを問い合わせるために使用されます。 - これにより、カスタムフォーマッタは、書式設定文字列で指定されたフラグに基づいて、異なる出力ロジックを適用できるようになります。
-
P
構造体とFmt
構造体の状態管理の統合:- 以前は、
P
構造体(プリンタの状態を管理する内部構造体)がwid
,wid_ok
,prec
,prec_ok
といった幅と精度の状態を直接保持していました。 - このコミットでは、これらのフィールドが
P
構造体から削除され、P.Width()
とP.Precision()
メソッドがp.fmt.wid
,p.fmt.wid_present
,p.fmt.prec
,p.fmt.prec_present
を参照するように変更されました。 - これは、書式設定の状態(フラグ、幅、精度など)を
Fmt
構造体(書式設定コンテキストを管理する内部構造体)に一元化するリファクタリングです。これにより、コードの重複が減り、状態管理がより明確になります。 Fmt.clearflags()
メソッドも更新され、wid
とprec
もクリアされるようになりました。
- 以前は、
-
P.Flag()
メソッドの実装:P
構造体にFlag(b int) bool
メソッドが追加され、Formatter
インターフェースの新しいメソッドを実装しています。- このメソッドは、引数
b
(フラグ文字のASCII値)に基づいて、p.fmt
内の対応するブールフラグ(minus
,plus
,sharp
,space
,zero
)の値を返します。
-
構造体メンバーの出力ロジックの改善 (
printField
):src/lib/fmt/print.go
のprintField
関数(リフレクションを使用して値をプリントする関数)内のreflect.StructKind
のケースが大幅に変更されました。- 以前は
p.doprint(field, true, false)
という呼び出しがありましたが、これは削除されました。 - 新しい実装では、構造体の各フィールドをループで処理し、
p.printField(getField(v, i))
を個別に呼び出すようになりました。 - 重要な変更点として、
donames := p.fmt.plus;
という行が追加され、+
フラグが設定されている場合にのみフィールド名を出力するロジックが導入されました。 - 各フィールドの間にスペース
' '
を追加することで、以前の「文字列が意図せず連結される」バグが修正されました。これにより、{abc def 123}
のように、各フィールドが適切に区切られて出力されるようになります。
-
書式設定文字列の解析 (
doprintf
):src/lib/fmt/print.go
のdoprintf
関数(書式設定文字列を解析し、値を出力する関数)も変更されました。- 書式設定フラグと幅/精度を解析する部分で、
p.fmt.clearflags()
が呼び出され、p.fmt.wid
,p.fmt.wid_present
,p.fmt.prec
,p.fmt.prec_present
が直接使用されるようになりました。これは、前述のP
とFmt
の状態管理統合の一環です。 - 以前の
p.wid
,p.wid_ok
,p.prec
,p.prec_ok
への直接代入は削除されました。
これらの変更により、fmt
パッケージはより強力で柔軟な書式設定機能を提供し、特に構造体のデバッグ出力とカスタムフォーマッタの利用において大きな改善がもたらされました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
src/lib/fmt/fmt_test.go
: 新しい機能のテストケースが追加されています。src/lib/fmt/format.go
:Fmt
構造体のclearflags
メソッドが更新されています。src/lib/fmt/print.go
:Formatter
インターフェースの定義、P
構造体の実装、およびprintField
とdoprintf
関数のロジックが変更されています。
src/lib/fmt/fmt_test.go
--- a/src/lib/fmt/fmt_test.go
+++ b/src/lib/fmt/fmt_test.go
@@ -6,6 +6,7 @@ package fmt
import (
"fmt";
+ "io";
"syscall";
"testing";
)
@@ -163,3 +164,77 @@ export func TestSprintf(t *testing.T) {
}
}
+type FlagPrinter struct { }
+func (*FlagPrinter) Format(f fmt.Formatter, c int) {
+ s := "%";
+ for i := 0; i < 128; i++ {
+ if f.Flag(i) {
+ s += string(i);
+ }
+ }
+ if w, ok := f.Width(); ok {
+ s += fmt.sprintf("%d", w);
+ }
+ if p, ok := f.Precision(); ok {
+ s += fmt.sprintf(".%d", p);
+ }
+ s += string(c);
+ io.WriteString(f, "["+s+"]");
+}
+
+type FlagTest struct {
+ in string;
+ out string;
+}
+
+var flagtests = []FlagTest {
+ FlagTest{ "%a", "[%a]" },
+ FlagTest{ "%-a", "[%-a]" },
+ FlagTest{ "%+a", "[%+a]" },
+ FlagTest{ "%#a", "[%#a]" },
+ FlagTest{ "% a", "[% a]" },
+ FlagTest{ "%0a", "[%0a]" },
+ FlagTest{ "%1.2a", "[%1.2a]" },
+ FlagTest{ "%-1.2a", "[%-1.2a]" },
+ FlagTest{ "%+1.2a", "[%+1.2a]" },
+ FlagTest{ "%-+1.2a", "[%+-1.2a]" },
+ FlagTest{ "%-1.2abc", "[%-1.2a]bc" },
+ FlagTest{ "%-+1.2abc", "[%+-1.2a]bc" },
+}
+
+export func TestFlagParser(t *testing.T) {
+ var flagprinter FlagPrinter;
+ for i := 0; i < len(flagtests); i++ {
+ tt := flagtests[i];
+ s := fmt.sprintf(tt.in, &flagprinter);
+ if s != tt.out {
+ t.Errorf("sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out);
+ }
+ }
+}
+
+export func TestStructPrinter(t *testing.T) {
+ var s struct {
+ a string;
+ b string;
+ c int;
+ };
+ s.a = "abc";
+ s.b = "def";
+ s.c = 123;
+ type Test struct {
+ fmt string;
+ out string;
+ }
+ var tests = []Test {
+ Test{ "%v", "{abc def 123}" },
+ Test{ "%+v", "{a=abc b=def c=123}" },
+ };
+ for i := 0; i < len(tests); i++ {
+ tt := tests[i];
+ out := fmt.sprintf(tt.fmt, s);
+ if out != tt.out {
+ t.Errorf("sprintf(%q, &s) = %q, want %q", tt.fmt, out, tt.out);
+ }
+ }
+}
src/lib/fmt/format.go
--- a/src/lib/fmt/format.go
+++ b/src/lib/fmt/format.go
@@ -50,7 +50,9 @@ export type Fmt struct {
}
func (f *Fmt) clearflags() {
+ f.wid = 0;
f.wid_present = false;
+ f.prec = 0;
f.prec_present = false;
f.minus = false;
f.plus = false;
src/lib/fmt/print.go
--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -23,6 +23,9 @@ export type Formatter interface {
Write(b *[]byte) (ret int, err *os.Error);
Width() (wid int, ok bool);
Precision() (prec int, ok bool);
+
+ // flags
+ Flag(int) bool;
}
type Format interface {
@@ -40,10 +43,6 @@ type P struct {
n int;
buf *[]byte;
fmt *Fmt;
-\twid int;
-\twid_ok bool;
-\tprec int;
-\tprec_ok bool;
}
func Printer() *P {
@@ -53,11 +52,27 @@ func Printer() *P {
}
func (p *P) Width() (wid int, ok bool) {
-\treturn p.wid, p.wid_ok
+ return p.fmt.wid, p.fmt.wid_present
}
func (p *P) Precision() (prec int, ok bool) {
-\treturn p.prec, p.prec_ok
+ return p.fmt.prec, p.fmt.prec_present
+}
+
+func (p *P) Flag(b int) bool {
+ switch b {
+ case '-':
+ return p.fmt.minus;
+ case '+':
+ return p.fmt.plus;
+ case '#':
+ return p.fmt.sharp;
+ case ' ':
+ return p.fmt.space;
+ case '0':
+ return p.fmt.zero;
+ }
+ return false
}
func (p *P) ensure(n int) {
@@ -369,7 +384,21 @@ func (p *P) printField(field reflect.Value) (was_string bool) {
}
case reflect.StructKind:
p.add('{');
-\t\tp.doprint(field, true, false);
+ v := field.(reflect.StructValue);
+ t := v.Type().(reflect.StructType);
+ donames := p.fmt.plus; // first p.printField clears flag
+ for i := 0; i < v.Len(); i++ {
+ if i > 0 {
+ p.add(' ')
+ }
+ if donames {
+ if name, typ, tag, off := t.Field(i); name != "" {
+ p.addstr(name);
+ p.add('=');
+ }
+ }
+ p.printField(getField(v, i));
+ }
p.add('}');
case reflect.InterfaceKind:
inter := field.(reflect.InterfaceValue).Get();
@@ -398,7 +427,8 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
continue;
}
i++;
-\t\t// flags
+\t\t// flags and widths
+\t\tp.fmt.clearflags();
F: for ; i < end; i++ {
switch format[i] {
case '#':
@@ -416,11 +446,10 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
}
}
// do we have 20 (width)?
-\t\tp.wid, p.wid_ok, i = parsenum(format, i, end);\n-\t\tp.prec_ok = false;\n+\t\tp.fmt.wid, p.fmt.wid_present, i = parsenum(format, i, end);\
// do we have .20 (precision)?
if i < end && format[i] == '.' {
-\t\t\tp.prec, p.prec_ok, i = parsenum(format, i+1, end);\n+\t\t\tp.fmt.prec, p.fmt.prec_present, i = parsenum(format, i+1, end);\
}
c, w = sys.stringtorune(format, i);\n i += w;\n@@ -445,12 +474,6 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
}
}
s := "";
-\t\tif p.wid_ok {\n-\t\t\tp.fmt.w(p.wid);\n-\t\t}\n-\t\tif p.prec_ok {\n-\t\t\tp.fmt.p(p.prec);\n-\t\t}\n \t\tswitch c {\n \t\t\t// bool\n \t\t\tcase 't':
コアとなるコードの解説
fmt_test.go
の変更点
-
FlagPrinter
型とTestFlagParser
関数:FlagPrinter
はfmt.Formatter
インターフェースを実装する新しい型です。- その
Format
メソッドは、渡されたfmt.Formatter
(この場合はP
構造体)のFlag()
,Width()
,Precision()
メソッドを呼び出し、どのフラグ、幅、精度が設定されているかを文字列として構築します。 TestFlagParser
は、様々な書式設定文字列(例:"%a"
,"%+a"
,"%1.2a"
)に対してFlagPrinter
を使用し、期待される出力が得られるかを確認します。これにより、Formatter
インターフェースの新しいFlag
メソッドが正しく機能すること、および幅と精度の解析が正しく行われることを検証します。
-
TestStructPrinter
関数:- このテストは、構造体の書式設定に関する新しい動作を検証します。
- 特に注目すべきは、
Test{ "%+v", "{a=abc b=def c=123}" }
というテストケースです。これは、%+v
という書式設定動詞を使用した場合に、構造体のフィールド名(a=
,b=
,c=
)が値の前に表示されることを期待しています。これは、このコミットの主要な機能追加の一つです。 Test{ "%v", "{abc def 123}" }
は、+
フラグがない場合はフィールド名が表示されないことを確認します。
format.go
の変更点
Fmt.clearflags()
メソッド:- このメソッドは、書式設定フラグをリセットするために使用されます。
- 変更前は
f.wid_present
とf.prec_present
のみをリセットしていましたが、このコミットでf.wid = 0;
とf.prec = 0;
が追加され、幅と精度の値自体もリセットされるようになりました。これにより、書式設定の状態がより完全に初期化されるようになります。
print.go
の変更点
-
Formatter
インターフェース:Flag(int) bool
という新しいメソッドが追加されました。このメソッドは、カスタムフォーマッタが書式設定フラグの状態を問い合わせるためのAPIを提供します。
-
P
構造体:wid
,wid_ok
,prec
,prec_ok
というフィールドが削除されました。これらの情報は、P
構造体が持つfmt *Fmt
フィールドを通じてFmt
構造体から取得されるようになりました。これにより、書式設定の状態管理が一元化され、コードの重複が削減されます。
-
P.Width()
およびP.Precision()
メソッド:- これらのメソッドは、
P
構造体自身のフィールドではなく、p.fmt
(Fmt
構造体へのポインタ)のフィールドを参照するように変更されました。これにより、書式設定の状態がFmt
構造体に集約されていることが明確になります。
- これらのメソッドは、
-
P.Flag(b int) bool
メソッド:Formatter
インターフェースの新しいFlag
メソッドの実装です。- 引数
b
(フラグ文字のASCII値)に応じて、p.fmt
内の対応するブールフラグ(minus
,plus
,sharp
,space
,zero
)の値を返します。これにより、カスタムフォーマッタは、書式設定時にどのフラグが指定されたかをプログラム的に判断できます。
-
P.printField(field reflect.Value)
メソッド:reflect.StructKind
(構造体)を処理する部分が大きく変更されました。- 以前は
p.doprint(field, true, false)
という汎用的なプリント関数を呼び出していましたが、これは削除されました。 - 新しいロジックでは、構造体の各フィールドをループで個別に処理します。
donames := p.fmt.plus;
という行が追加され、+
フラグが設定されている場合にのみフィールド名を出力する条件が導入されました。if donames { ... p.addstr(name); p.add('='); }
の部分で、フィールド名と=
記号が追加されます。if i > 0 { p.add(' ') }
という行が追加され、各フィールドの間にスペースが挿入されるようになりました。これにより、以前の「文字列が意図せず連結される」バグが修正され、構造体メンバーが適切に区切られて出力されるようになります。
-
P.doprintf(format string, v reflect.StructValue)
メソッド:- 書式設定文字列の解析部分が変更されました。
- フラグと幅/精度を解析する前に
p.fmt.clearflags()
が呼び出されるようになりました。これにより、各書式設定動詞の処理前にフラグの状態が確実にリセットされます。 - 幅と精度の解析結果を
p.wid
,p.wid_ok
,p.prec
,p.prec_ok
に直接代入する代わりに、p.fmt.wid
,p.fmt.wid_present
,p.fmt.prec
,p.fmt.prec_present
に直接代入するように変更されました。これは、Fmt
構造体への状態の一元化を反映しています。 - 以前存在した、
p.wid_ok
やp.prec_ok
に基づいてp.fmt.w()
やp.fmt.p()
を呼び出すロジックが削除されました。これは、幅と精度の設定がFmt
構造体内で直接行われるようになったため、不要になったためです。
これらの変更は、Go言語のfmt
パッケージが、より洗練された書式設定機能を提供し、内部的な状態管理を改善するための重要なステップであったことを示しています。
関連リンク
- Go言語の
fmt
パッケージ公式ドキュメント: https://pkg.go.dev/fmt - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語のインターフェースに関する公式ドキュメント: https://go.dev/tour/methods/10
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の初期開発に関する情報 (Go Blogなど): https://go.dev/blog/
- Go言語の
fmt
パッケージの歴史に関する議論 (Go Issuesなど): https://github.com/golang/go/issues - Go言語の
fmt
パッケージの内部実装に関する解説記事 (非公式): (特定の記事は参照していませんが、一般的なGoのfmt
パッケージの内部動作に関する知識を基にしています。)