[インデックス 1049] ファイルの概要
このコミットは、Go言語の標準ライブラリである fmt
パッケージ内のプリンタ(P
struct)の内部実装を大幅に改修し、再帰的なprintf
呼び出しを安全に処理できるように準備するものです。具体的には、P
structをWriter
インターフェースに準拠させ、fmt
パッケージの主要な出力関数(fprintf
, printf
, sprintf
など)が呼び出されるたびに新しいP
インスタンスを生成するように変更しています。これにより、カスタムフォーマット動詞(custom verbs)内でfmt
関数を再帰的に使用する際の競合や状態破壊を防ぎます。
コミット
commit 3200b06b14ba24551921cfa76da94c374d8f3e8d
Author: Rob Pike <r@golang.org>
Date: Tue Nov 4 13:57:21 2008 -0800
prepare for recursive printfs
R=rsc
DELTA=31 (9 added, 6 deleted, 16 changed)
OCL=18470
CL=18472
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3200b06b14ba24551921cfa76da94c374d8f3e8d
元コミット内容
--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -18,13 +18,13 @@ import (
const Runeself = 0x80
const AllocSize = 32
-export type P struct {
+type P struct {
n int;
buf *[]byte;
fmt *Fmt;
}
-export func Printer() *P {
+func Printer() *P {
p := new(P);
p.fmt = fmt.New();
return p;
@@ -74,8 +74,11 @@ func (p *P) add(c int) {
}
}
-func (p *P) reset() {
- p.n = 0;
+// Implement Write so we can call fprintf on a P, for
+// recursive use in custom verbs.
+func (p *P) Write(b *[]byte) (ret int, err *os.Error) {
+ p.addbytes(b, 0, len(b));
+ return len(b), nil;
}
export type Writer interface {
@@ -87,46 +90,46 @@ func (p *P) doprint(v reflect.StructValue, addspace, addnewline bool);
// These routines end in 'f' and take a format string.
-func (p *P) fprintf(w Writer, format string, a ...) (n int, error *os.Error) {
+export func fprintf(w Writer, format string, a ...) (n int, error *os.Error) {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
+\tp := Printer();
p.doprintf(format, v);
n, error = w.Write(p.buf[0:p.n]);
-\tp.reset();
return n, error;
}
-func (p *P) printf(format string, v ...) (n int, errno *os.Error) {
- n, errno = p.fprintf(os.Stdout, format, v);
+export func printf(format string, v ...) (n int, errno *os.Error) {
+\tn, errno = fprintf(os.Stdout, format, v);
return n, errno;
}
-func (p *P) sprintf(format string, v ...) string {
+export func sprintf(format string, v ...) string {
+\tp := Printer();
p.doprintf(format, reflect.NewValue(v).(reflect.StructValue));
s := string(p.buf)[0 : p.n];
-\tp.reset();
return s;
}
// These routines do not take a format string and add spaces only
// when the operand on neither side is a string.
-func (p *P) fprint(w Writer, a ...) (n int, error *os.Error) {
+export func fprint(w Writer, a ...) (n int, error *os.Error) {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
+\tp := Printer();
p.doprint(v, false, false);
n, error = w.Write(p.buf[0:p.n]);
-\tp.reset();
return n, error;
}
-func (p *P) print(v ...) (n int, errno *os.Error) {
- n, errno = p.fprint(os.Stdout, v);
+export func print(v ...) (n int, errno *os.Error) {
+\tn, errno = fprint(os.Stdout, v);
return n, errno;
}
-func (p *P) sprint(v ...) string {
+export func sprint(v ...) string {
+\tp := Printer();
p.doprint(reflect.NewValue(v).(reflect.StructValue), false, false);
s := string(p.buf)[0 : p.n];
-\tp.reset();
return s;
}
@@ -134,23 +137,23 @@ func (p *P) sprint(v ...) string {
// always add spaces between operands, and add a newline
// after the last operand.
-func (p *P) fprintln(w Writer, a ...) (n int, error *os.Error) {
+export func fprintln(w Writer, a ...) (n int, error *os.Error) {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
+\tp := Printer();
p.doprint(v, true, true);
n, error = w.Write(p.buf[0:p.n]);
-\tp.reset();
return n, error;
}
-func (p *P) println(v ...) (n int, errno *os.Error) {
- n, errno = p.fprintln(os.Stdout, v);
+export func println(v ...) (n int, errno *os.Error) {
+\tn, errno = fprintln(os.Stdout, v);
return n, errno;
}
-func (p *P) sprintln(v ...) string {
+export func sprintln(v ...) string {
+\tp := Printer();
p.doprint(reflect.NewValue(v).(reflect.StructValue), true, true);
s := string(p.buf)[0 : p.n];
-\tp.reset();
return s;
}
変更の背景
このコミットの主な背景は、Go言語のfmt
パッケージが提供するフォーマット機能において、再帰的な呼び出しを安全かつ効率的に処理できるようにすることです。特に、カスタムフォーマット動詞(fmt.Formatter
インターフェースなどを実装した型)が、自身の内部でさらにfmt.Sprintf
などの関数を呼び出すようなシナリオを想定しています。
従来のfmt
パッケージの内部実装では、プリンタの状態(P
struct)が再利用されていました。これは、パフォーマンスの観点からは有利ですが、あるfmt
関数が別のfmt
関数を呼び出すような再帰的な状況が発生した場合、同じプリンタの状態を共有することになり、出力バッファの競合や状態の破壊といった問題を引き起こす可能性がありました。
このコミットは、このような再帰的な呼び出しが発生しても、それぞれの呼び出しが独立したプリンタの状態を持つようにすることで、安全性を確保し、fmt
パッケージの柔軟性を高めることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念とfmt
パッケージの動作に関する知識が必要です。
fmt
パッケージ: Go言語の標準ライブラリの一つで、フォーマットされたI/O(入出力)を提供します。Printf
,Sprintf
,Fprint
などの関数を通じて、様々な型の値を整形して出力したり、文字列に変換したりする機能を提供します。- フォーマット動詞 (Format Verbs):
fmt
パッケージの関数で使用される%v
,%s
,%d
などの特殊な記号で、値の表示形式を指定します。 reflect
パッケージ: Go言語のリフレクション機能を提供します。実行時に型情報や値の操作を可能にします。fmt
パッケージは、引数の型を動的に検査し、適切なフォーマット処理を行うためにreflect
パッケージを内部的に利用しています。- インターフェース (Interfaces): Go言語における型が満たすべき振る舞いを定義するものです。
fmt
パッケージでは、特定のインターフェース(例:Stringer
,Formatter
,Writer
)を実装することで、カスタム型がfmt
関数によってどのようにフォーマットされるかを制御できます。Writer
インターフェース:Write([]byte) (n int, err error)
メソッドを持つインターフェースです。os.Stdout
やbytes.Buffer
など、バイト列を書き込むことができるあらゆるものがこのインターフェースを実装しています。fmt.Fprintf
などの関数は、Writer
インターフェースを満たす任意のオブジェクトに出力できます。
- カスタムフォーマット動詞 (Custom Verbs): ユーザー定義の型が
fmt.Formatter
インターフェースを実装することで、独自のフォーマットロジックを提供できます。例えば、%x
で構造体の特定のフィールドを16進数で表示するといったことが可能です。
技術的詳細
このコミットの技術的な核心は、fmt
パッケージの内部プリンタであるP
structのインスタンス管理と、Writer
インターフェースの実装にあります。
-
P
structの非エクスポート化とPrinter()
関数の変更:export type P struct {
からtype P struct {
へ変更され、P
structはパッケージ外部から直接アクセスできなくなりました。これは、P
がfmt
パッケージの内部実装の詳細であることを明確にし、外部からの不適切な利用を防ぐためのカプセル化の一環です。- 同様に、
export func Printer() *P {
からfunc Printer() *P {
へ変更され、Printer()
関数も非エクスポート化されました。この関数はP
の新しいインスタンスを生成する役割を担いますが、これも内部的なヘルパー関数となりました。
-
P.reset()
メソッドの削除:- 以前のバージョンでは、
P
structは再利用され、各フォーマット操作の前にreset()
メソッドを呼び出して内部状態(特にn
、書き込まれたバイト数)をリセットしていました。 - このコミットでは、後述するように各
fmt
関数呼び出しで新しいP
インスタンスが生成されるようになるため、reset()
メソッドは不要となり削除されました。
- 以前のバージョンでは、
-
P
structへのWrite
メソッドの実装:- これが最も重要な変更点の一つです。
P
structに以下のWrite
メソッドが追加されました。func (p *P) Write(b *[]byte) (ret int, err *os.Error) { p.addbytes(b, 0, len(b)); return len(b), nil; }
- このメソッドの追加により、
P
structはGoの標準Writer
インターフェース(Write([]byte) (n int, err error)
)を満たすようになりました。 - この変更の意図は、コメントに「Implement Write so we can call fprintf on a P, for recursive use in custom verbs.」(カスタム動詞での再帰的な使用のために、
P
上でfprintf
を呼び出せるようにWrite
を実装する)と明記されています。 - つまり、
P
自体がWriter
として振る舞えるようになったことで、fmt
パッケージの内部で、別のfmt
関数(例:fprintf
)を呼び出す際に、その出力先として新しいP
インスタンスを渡すことが可能になります。これにより、再帰的な呼び出しが独立したプリンタの状態を持つことが保証されます。
- これが最も重要な変更点の一つです。
-
主要な
fmt
関数(fprintf
,printf
,sprintf
など)の変更:- これらの関数は、以前は
P
structのメソッドとして定義されていましたが、このコミットでパッケージレベルの関数(export func ...
)に変更されました。 - そして、これらの関数の内部で
p := Printer()
という行が追加されました。これは、各fmt
関数が呼び出されるたびに、新しいP
インスタンスを生成することを意味します。 - これにより、
fmt
関数が再帰的に呼び出された場合でも、それぞれの呼び出しが独自のP
インスタンス(独自の出力バッファと状態)を持つため、互いに干渉することなく安全に処理を進めることができます。 p.reset()
の呼び出しが削除されたのは、新しいP
インスタンスが常にクリーンな状態で開始されるためです。
- これらの関数は、以前は
これらの変更により、fmt
パッケージはより堅牢になり、カスタムフォーマットロジック内でfmt
関数を再帰的に使用するような高度なシナリオにも対応できるようになりました。
コアとなるコードの変更箇所
変更はすべて src/lib/fmt/print.go
ファイル内で行われています。
P
structとPrinter()
関数のexport
キーワード削除:-export type P struct { +type P struct { // ... -export func Printer() *P { +func Printer() *P {
P.reset()
メソッドの削除:-func (p *P) reset() { - p.n = 0; -}// ...
P
structへのWrite
メソッドの追加:// Implement Write so we can call fprintf on a P, for // recursive use in custom verbs. func (p *P) Write(b *[]byte) (ret int, err *os.Error) { p.addbytes(b, 0, len(b)); return len(b), nil; }
- 主要な
fmt
関数の変更(例:fprintf
):- メソッドからパッケージレベル関数への変更 (
func (p *P) fprintf
->export func fprintf
) p := Printer()
の追加p.reset()
の削除
同様の変更が-func (p *P) fprintf(w Writer, format string, a ...) (n int, error *os.Error) { +export func fprintf(w Writer, format string, a ...) (n int, error *os.Error) { v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue); +\tp := Printer(); // 新しいPインスタンスを生成 p.doprintf(format, v); n, error = w.Write(p.buf[0:p.n]); -\tp.reset(); // resetの削除 return n, error; }
printf
,sprintf
,fprint
,print
,sprint
,fprintln
,println
,sprintln
の各関数にも適用されています。 - メソッドからパッケージレベル関数への変更 (
コアとなるコードの解説
このコミットの核心は、fmt
パッケージの内部プリンタP
が、再帰的なフォーマット操作において独立した状態を保つように設計された点です。
以前は、fmt
パッケージの関数が内部でP
インスタンスを共有し、reset()
メソッドで状態をクリアして再利用していました。しかし、これはカスタムフォーマット動詞などがfmt.Sprintf
などを呼び出すような再帰的なシナリオでは問題を引き起こす可能性がありました。例えば、あるfmt.Sprintf
の呼び出し中に、その内部で別のfmt.Sprintf
が呼び出されると、両者が同じP
インスタンスの出力バッファを操作しようとし、結果が混ざったり壊れたりする恐れがありました。
このコミットでは、この問題を解決するために以下の戦略が取られました。
P
のWriter
インターフェース実装:P
structにWrite([]byte) (int, error)
メソッドが追加されました。これにより、P
はGoの標準io.Writer
インターフェースを満たすようになります。これは、P
インスタンス自体がバイト列の書き込み先として機能できることを意味します。- 各
fmt
関数呼び出しでの新しいP
インスタンス生成:fprintf
,printf
,sprintf
などの主要なfmt
関数は、呼び出されるたびにp := Printer()
という行で新しいP
インスタンスを生成するようになりました。これにより、各トップレベルのfmt
関数呼び出しは、独自のクリーンなプリンタ状態(出力バッファなど)を持つことになります。 - 再帰的な呼び出しの分離:
- もし、あるカスタムフォーマット動詞が
fmt.Sprintf
を呼び出したとします。このfmt.Sprintf
は、新しいP
インスタンスを生成して処理を開始します。 - この新しい
P
インスタンスは、Writer
インターフェースを実装しているため、そのWrite
メソッドを通じてバイト列を受け取ることができます。 - これにより、外側の
fmt
呼び出しと内側のfmt
呼び出しが、それぞれ独立したP
インスタンス上で動作し、出力バッファの競合がなくなります。
- もし、あるカスタムフォーマット動詞が
この設計変更により、fmt
パッケージはより複雑なフォーマットシナリオ、特に再帰的なフォーマット要求に対して、堅牢かつ予測可能な振る舞いを提供するようになりました。P
structとPrinter()
関数が非エクスポート化されたのは、これらの変更がfmt
パッケージの内部実装の詳細であり、外部から直接操作されるべきではないという設計思想を反映しています。
関連リンク
- Go言語の
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt - Go言語の
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語の
io
パッケージのドキュメント(Writer
インターフェースについて): https://pkg.go.dev/io
参考にした情報源リンク
- Go言語のソースコード(
src/lib/fmt/print.go
) - Go言語のコミット履歴
- Go言語の
fmt
パッケージに関する一般的な解説記事やチュートリアル(再帰的なprintf
の概念を理解するため)