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

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

このコミットは、Go言語の標準ライブラリであるfmtパッケージのPrintf系関数(Printf, Sprintf, Fprintfなど)に、引数へのランダムアクセス(明示的な引数インデックス指定)機能を追加するものです。これにより、フォーマット文字列内で引数の順序を自由に指定したり、同じ引数を複数回参照したりすることが可能になります。また、コードベース内で「field」という用語が誤用されていた箇所を「arg」または「argument」に修正し、コードの可読性と正確性を向上させています。

コミット

commit 7472ce0e58b6c73902af51fc0aab13bf8e90aa80
Author: Rob Pike <r@golang.org>
Date:   Fri May 24 15:49:26 2013 -0700

    fmt.Printf: introduce notation for random access to arguments.
    This text is added to doc.go:
    
            Explicit argument indexes:
    
            In Printf, Sprintf, and Fprintf, the default behavior is for each
            formatting verb to format successive arguments passed in the call.
            However, the notation [n] immediately before the verb indicates that the
            nth one-indexed argument is to be formatted instead. The same notation
            before a '*' for a width or precision selects the argument index holding
            the value. After processing a bracketed expression [n], arguments n+1,
            n+2, etc. will be processed unless otherwise directed.
    
            For example,
                    fmt.Sprintf("%[2]d %[1]d\n", 11, 22)
            will yield "22, 11", while
                    fmt.Sprintf("%[3]*[2].*[1]f", 12.0, 2, 6),
            equivalent to
                    fmt.Sprintf("%6.2f", 12.0),
            will yield " 12.00". Because an explicit index affects subsequent verbs,
            this notation can be used to print the same values multiple times
            by resetting the index for the first argument to be repeated:
                    fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
            will yield "16 17 0x10 0x11".
    
    The notation chosen differs from that in C, but I believe it's easier to read
    and to remember (we're indexing the arguments), and compatibility with
    C's printf was never a strong goal anyway.
    
    While we're here, change the word "field" to "arg" or "argument" in the
    code; it was being misused and was confusing.
    
    R=rsc, bradfitz, rogpeppe, minux.ma, peter.armitage
    CC=golang-dev
    https://golang.org/cl/9680043

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

https://github.com/golang/go/commit/7472ce0e58b6c73902af51fc0aab13bf8e90aa80

元コミット内容

fmt.Printf: 引数へのランダムアクセス記法を導入。 このテキストはdoc.goに追加されます:

明示的な引数インデックス:

PrintfSprintfFprintfでは、デフォルトの動作として、各フォーマット動詞が呼び出しで渡された連続する引数をフォーマットします。しかし、動詞の直前に[n]という記法を使用すると、代わりにn番目の1から始まるインデックスの引数がフォーマットされます。幅や精度を示す*の前に同じ記法を使用すると、その値を持つ引数のインデックスが選択されます。[n]という括弧で囲まれた式を処理した後、特に指示がない限り、引数n+1、n+2などが処理されます。

例: fmt.Sprintf("%[2]d %[1]d\n", 11, 22) は "22, 11" を生成します。一方、 fmt.Sprintf("%[3]*[2].*[1]f", 12.0, 2, 6) は、 fmt.Sprintf("%6.2f", 12.0) と同等で、" 12.00" を生成します。明示的なインデックスは後続の動詞に影響を与えるため、この記法は、繰り返される最初の引数のインデックスをリセットすることで、同じ値を複数回出力するために使用できます: fmt.Sprintf("%d %d %#[1]x %#x", 16, 17) は "16 17 0x10 0x11" を生成します。

選択された記法はC言語のものとは異なりますが、読みやすく覚えやすい(引数をインデックスで指定する)と私は信じています。C言語のprintfとの互換性は、そもそも強い目標ではありませんでした。

ついでに、コード内の「field」という単語を「arg」または「argument」に変更します。これは誤用されており、混乱を招いていました。

変更の背景

Go言語のfmtパッケージは、C言語のprintfに似た強力なフォーマット機能を提供しますが、従来のprintfにはいくつかの制約がありました。特に、引数の順序はフォーマット文字列に現れる順序に厳密に従う必要があり、同じ引数を複数回出力したり、フォーマット文字列内で引数の順序を入れ替えたりすることができませんでした。

この制約は、特に国際化(i18n)の文脈で問題となることがありました。異なる言語では、文の構造や単語の順序が異なるため、同じ意味を持つメッセージでも引数の配置が変わることがよくあります。例えば、「XはY個あります」というメッセージを英語と日本語で表現する場合、引数XとYの順序が逆になることがあります。従来のfmt.Printfでは、このような場合にフォーマット文字列を複数用意するか、引数を事前に並べ替える必要があり、コードの複雑さや保守性の低下を招いていました。

このコミットは、このような問題を解決するために、フォーマット文字列内で明示的に引数のインデックスを指定できる機能(%[n]記法)を導入しました。これにより、開発者はフォーマット文字列の柔軟性を高め、より簡潔で国際化に対応しやすいコードを書くことができるようになりました。

また、コードベース内でpp構造体のメンバー名としてfieldが使われていましたが、これはfmtパッケージが処理する対象が「フィールド」ではなく「引数(argument)」であるため、誤解を招く可能性がありました。このコミットでは、この用語をより正確な「arg」または「argument」に修正し、コードのセマンティクスを改善しています。

前提知識の解説

Go言語の fmt パッケージ

fmtパッケージは、Go言語におけるI/Oフォーマット機能を提供します。C言語のprintfscanfに似た関数群を持ち、様々なデータ型を文字列に変換したり、文字列からデータを解析したりするために使用されます。

  • フォーマット動詞 (Verbs): %d (整数), %s (文字列), %f (浮動小数点数), %v (デフォルトフォーマット), %T (型名) など、引数の型に応じて出力形式を指定する記号です。
  • フラグ (Flags): フォーマット動詞の前に置かれ、出力の挙動を制御します。例えば、%#vはGoの構文で値を表示し、%+vは構造体のフィールド名を表示します。
  • 幅 (Width): 出力される文字列の最小幅を指定します。例えば、%5dは整数を少なくとも5文字幅で表示し、必要に応じて空白でパディングします。
  • 精度 (Precision): 浮動小数点数の小数点以下の桁数や、文字列の最大文字数などを指定します。例えば、%.2fは浮動小数点数を小数点以下2桁で表示します。
  • Printf系関数:
    • fmt.Printf(format string, a ...interface{}) (n int, err error): 標準出力にフォーマットされた文字列を出力します。
    • fmt.Sprintf(format string, a ...interface{}) string: フォーマットされた文字列を返します。
    • fmt.Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error): 指定されたio.Writerにフォーマットされた文字列を出力します。

C言語の printf における引数インデックス指定

C言語のprintf標準には、POSIX拡張として引数インデックス指定の機能があります。これは%n$という形式で、n番目の引数を参照します。例えば、printf("%2$d %1$d\n", 11, 22);は「22 11」と出力されます。Go言語で導入された%[n]記法は、このC言語の機能に似ていますが、記法が異なります。Goの設計者は、Cの記法よりも[n]の方が直感的で覚えやすいと考えたようです。

技術的詳細

このコミットの主要な変更は、fmtパッケージの内部実装、特にprint.goファイルにおけるdoPrintf関数のロジックにあります。

  1. 引数インデックスのパース:

    • 新しい記法%[n]を解析するために、parseArgNumber関数が導入されました。この関数は、フォーマット文字列から[n]の部分を抽出し、nの値を(1から始まるインデックスとして)解析します。
    • argNumber関数は、現在の引数番号(argNum)とフォーマット文字列の現在の位置を受け取り、%[n]記法が存在するかどうかをチェックします。存在する場合、parseArgNumberを呼び出して新しい引数インデックスを取得し、argNumを更新します。これにより、次にフォーマットされる引数が指定されたインデックスの引数になります。
  2. pp構造体の変更:

    • pp構造体は、fmtパッケージの内部でフォーマット処理の状態を保持するために使用されます。
    • pp.fieldというメンバーがpp.argにリネームされました。これは、fmtが処理する対象が「引数」であることを明確にするための用語の修正です。
    • pp.reorderedという新しいブール型フィールドが追加されました。これは、フォーマット文字列で引数の順序が変更された(%[n]記法が使用された)場合にtrueに設定されます。
    • pp.goodArgNumという新しいブール型フィールドが追加されました。これは、最後に解析された引数インデックスが有効であったかどうかを示します。無効なインデックスが指定された場合(例: 範囲外のインデックス)、%!(BADARGNUM)というエラーメッセージが生成されます。
  3. doPrintfのロジック変更:

    • doPrintf関数は、フォーマット文字列を走査し、各フォーマット動詞を処理するメインのループを含んでいます。
    • このループ内で、フォーマット動詞のフラグや幅、精度を解析する前に、p.argNumberが呼び出されるようになりました。これにより、%[n]記法が検出された場合、次に処理される引数のインデックスが動的に変更されます。
    • 幅や精度が*で指定され、その値が引数から取得される場合(例: %*s)、その*の直後にもp.argNumberが呼び出される可能性があります。これは、%[3]*[2].*[1]fのような複雑なケースに対応するためです。
    • 引数が不足している場合(argNum >= len(a))や、無効な引数インデックスが指定された場合(!p.goodArgNum)には、それぞれ%!(MISSING)%!(BADARGNUM)といったエラーメッセージが生成されるようになりました。
    • doPrintfの最後に、!p.reordered && argNum < len(a)という条件が追加されました。これは、引数の順序が変更されていない(reorderedfalse)にもかかわらず、まだ処理されていない余分な引数がある場合に、%!(EXTRA ...)というエラーメッセージを出力するためのものです。引数の順序が変更された場合は、すべての引数が使用されたかどうかを検出するのがコスト高になるため、このチェックはスキップされます。
  4. 用語の統一:

    • src/pkg/fmt/print.gosrc/pkg/fmt/scan.go全体で、fieldという変数名やコメント内の用語がargまたはargumentに統一されました。これにより、コードの意図がより明確になりました。例えば、printField関数はprintArgにリネームされ、intFromArg関数も引数名をfieldnumからargNumに変更しています。

これらの変更により、fmt.Printfはより柔軟なフォーマット機能を提供し、特に動的なフォーマット文字列の生成や国際化のシナリオにおいて、その有用性が向上しました。

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

src/pkg/fmt/doc.go (ドキュメントの追加)

--- a/src/pkg/fmt/doc.go
+++ b/src/pkg/fmt/doc.go
@@ -118,6 +118,28 @@
 	convert the value before recurring:
 		func (x X) String() string { return Sprintf("<%s>", string(x)) }
 
+	Explicit argument indexes:
+
+	In Printf, Sprintf, and Fprintf, the default behavior is for each
+	formatting verb to format successive arguments passed in the call.
+	However, the notation [n] immediately before the verb indicates that the
+	nth one-indexed argument is to be formatted instead. The same notation
+	before a '*' for a width or precision selects the argument index holding
+	the value. After processing a bracketed expression [n], arguments n+1,
+	n+2, etc. will be processed unless otherwise directed.
+
+	For example,
+		fmt.Sprintf("%[2]d %[1]d\n", 11, 22)
+	will yield "22, 11", while
+		fmt.Sprintf("%[3]*[2].*[1]f", 12.0, 2, 6),
+	equivalent to
+		fmt.Sprintf("%6.2f", 12.0),
+	will yield " 12.00". Because an explicit index affects subsequent verbs,
+	this notation can be used to print the same values multiple times
+	by resetting the index for the first argument to be repeated:
+		fmt.Sprintf("%d %d %#[1]x %#x", 16, 17)
+	will yield "16 17 0x10 0x11".
+
 	Format errors:
 
 	If an invalid argument is given for a verb, such as providing
@@ -133,6 +155,8 @@
 		Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC)
 			Printf("%*s", 4.5, "hi"):  %!(BADWIDTH)hi
 			Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi
+		Invalid or out-of-range argument index: %!(BADARGNUM)
+			Printf("%*[2]d", 7):       %d(BADARGNUM)
 
 	All errors begin with the string "%!" followed sometimes
 	by a single character (the verb) and end with a parenthesized

src/pkg/fmt/print.go (主要なロジック変更)

pp構造体の変更:

--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -109,13 +110,17 @@ type pp struct {
 	panicking bool
 	erroring  bool // printing an error condition
 	buf       buffer
-	// field holds the current item, as an interface{}.
-	field interface{}
+	// arg holds the current item, as an interface{}.
+	arg interface{}
 	// value holds the current item, as a reflect.Value, and will be
 	// the zero Value if the item has not been reflected.
-	value   reflect.Value
-	runeBuf [utf8.UTFMax]byte
-	fmt     fmt
+	value reflect.Value
+	// reordered records whether the format string used argument reordering.
+	reordered bool
+	// goodArgNum records whether the last reordering directive was valid.
+	goodArgNum bool
+	runeBuf    [utf8.UTFMax]byte
+	fmt        fmt
 }

新しいヘルパー関数 parseArgNumberargNumber:

--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -1015,19 +1020,57 @@ BigSwitch:
 	return wasString
 }
 
-// intFromArg gets the fieldnumth element of a. On return, isInt reports whether the argument has type int.\n-func intFromArg(a []interface{}, end, i, fieldnum int) (num int, isInt bool, newi, newfieldnum int) {\n-\tnewi, newfieldnum = end, fieldnum\n-\tif i < end && fieldnum < len(a) {\n-\t\tnum, isInt = a[fieldnum].(int)\n-\t\tnewi, newfieldnum = i+1, fieldnum+1\n+// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has type int.\n+func intFromArg(a []interface{}, end, i, argNum int) (num int, isInt bool, newi, newArgNum int) {\n+	newi, newArgNum = end, argNum
+	if i < end && argNum < len(a) {
+		num, isInt = a[argNum].(int)
+		newi, newArgNum = i+1, argNum+1
 	}
 	return
 }
 
+// parseArgNumber returns the value of the bracketed number, minus 1
+// (explicit argument numbers are one-indexed but we want zero-indexed).\n+// The opening bracket is known to be present at format[0].\n+// The returned values are the index, the number of bytes to consume
+// up to the closing paren, if present, and whether the number parsed
+// ok. The bytes to consume will be 1 if no closing paren is present.\n+func parseArgNumber(format string) (index int, wid int, ok bool) {
+	// Find closing parenthesis
+	for i := 1; i < len(format); i++ {
+		if format[i] == ']' {
+			width, ok, newi := parsenum(format, 1, i)
+			if !ok || newi != i {
+				return 0, i + 1, false
+			}
+			return width - 1, i + 1, true // arg numbers are one-indexed and skip paren.
+		}
+	}
+	return 0, 1, false
+}
+
+// argNumber returns the next argument to evaluate, which is either the value of the passed-in
+// argNum or the value of the bracketed integer that begins format[i:]. It also returns
+// the new value of i, that is, the index of the next byte of the format to process.\n+func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int) {
+	p.goodArgNum = true
+	if len(format) <= i || format[i] != '[' {
+		return argNum, i
+	}
+	p.reordered = true
+	index, wid, ok := parseArgNumber(format[i:])
+	if ok && 0 <= index && index < numArgs {
+		return index, i + wid
+	}
+	p.goodArgNum = false
+	return argNum, i + wid
+}
+
 func (p *pp) doPrintf(format string, a []interface{}) {
 	end := len(format)
-\tfieldnum := 0 // we process one field per non-trivial format
+\targNum := 0 // we process one argument per non-trivial format
+\tp.reordered = false
 	for i := 0; i < end; {
 		lasti := i
 		for i < end && format[i] != '%' {
@@ -1043,7 +1086,8 @@ func (p *pp) doPrintf(format string, a []interface{}) {
 
 		// Process one verb
 		i++
-\t\t// flags and widths
+\n+\t\t// Do we have flags?\n \t\tp.fmt.clearflags()\n \tF:\n \t\tfor ; i < end; i++ {
@@ -1062,22 +1106,29 @@ func (p *pp) doPrintf(format string, a []interface{}) {
 				break F
 			}
 		}
-\t\t// do we have width?
+\n+\t\t// Do we have an explicit argument index?\n+\t\targNum, i = p.argNumber(argNum, format, i, len(a))\n+\n+\t\t// Do we have width?\n \t\tif i < end && format[i] == '*' {
-\t\t\tp.fmt.wid, p.fmt.widPresent, i, fieldnum = intFromArg(a, end, i, fieldnum)
+\t\t\tp.fmt.wid, p.fmt.widPresent, i, argNum = intFromArg(a, end, i, argNum)
 \t\t\tif !p.fmt.widPresent {
 \t\t\t\tp.buf.Write(badWidthBytes)
 \t\t\t}
+\t\t\targNum, i = p.argNumber(argNum, format, i, len(a)) // We consumed []; another can follow here.\n \t\t} else {
 \t\t\tp.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end)
 \t\t}
-\t\t// do we have precision?
+\n+\t\t// Do we have precision?\n \t\tif i+1 < end && format[i] == '.' {
 \t\t\tif format[i+1] == '*' {
-\t\t\t\tp.fmt.prec, p.fmt.precPresent, i, fieldnum = intFromArg(a, end, i+1, fieldnum)
+\t\t\t\tp.fmt.prec, p.fmt.precPresent, i, argNum = intFromArg(a, end, i+1, argNum)
 \t\t\t\tif !p.fmt.precPresent {
 \t\t\t\t\tp.buf.Write(badPrecBytes)
 \t\t\t\t}
+\t\t\t\targNum, i = p.argNumber(argNum, format, i, len(a)) // We consumed []; another can follow here.\n \t\t\t} else {
 \t\t\t\tp.fmt.prec, p.fmt.precPresent, i = parsenum(format, i+1, end)
 \t\t\t\tif !p.fmt.precPresent {
@@ -1097,30 +1148,38 @@ func (p *pp) doPrintf(format string, a []interface{}) {
 \t\t\tp.buf.WriteByte('%') // We ignore width and prec.\n \t\t\tcontinue\n \t\t}\n-\t\tif fieldnum >= len(a) { // out of operands
+\t\tif !p.goodArgNum {
+\t\t\tp.buf.WriteByte('%')
+\t\t\tp.add(c)
+\t\t\tp.buf.Write(badArgNum)
+\t\t\tcontinue
+\t\t} else if argNum >= len(a) { // out of operands
 \t\t\tp.buf.WriteByte('%')
 \t\t\tp.add(c)
 \t\t\tp.buf.Write(missingBytes)
 \t\t\tcontinue
 \t\t}\n-\t\tfield := a[fieldnum]
-\t\tfieldnum++
+\t\targ := a[argNum]
+\t\targNum++
 
 \t\tgoSyntax := c == 'v' && p.fmt.sharp
 \t\tplus := c == 'v' && p.fmt.plus
-\t\tp.printField(field, c, plus, goSyntax, 0)
+\t\tp.printArg(arg, c, plus, goSyntax, 0)
 \t}\n 
-\tif fieldnum < len(a) {
+\n+\t// Check for extra arguments unless the call accessed the arguments
+\t// out of order, in which case it's too expensive to detect if they've all
+\t// been used and arguably OK if they're not.\n+\tif !p.reordered && argNum < len(a) {
 \t\tp.buf.Write(extraBytes)
-\t\tfor ; fieldnum < len(a); fieldnum++ {
-\t\t\tfield := a[fieldnum]
-\t\t\tif field != nil {
-\t\t\t\tp.buf.WriteString(reflect.TypeOf(field).String())
+\t\tfor ; argNum < len(a); argNum++ {
+\t\t\targ := a[argNum]
+\t\t\tif arg != nil {
+\t\t\t\tp.buf.WriteString(reflect.TypeOf(arg).String())
 \t\t\t\tp.buf.WriteByte('=')
 \t\t\t}\n-\t\t\tp.printField(field, 'v', false, false, 0)
-\t\t\tif fieldnum+1 < len(a) {
+\t\t\tp.printArg(arg, 'v', false, false, 0)
+\t\t\tif argNum+1 < len(a) {
 \t\t\t\tp.buf.Write(commaSpaceBytes)
 \t\t\t}\n \t\t}

src/pkg/fmt/fmt_test.go (テストケースの追加)

新しいテストケースreorderTestsが追加され、TestReorder関数で実行されます。これにより、引数インデックス指定機能の様々なシナリオ(正常系、エラー系)が検証されます。

--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -539,6 +539,42 @@ func TestSprintf(t *testing.T) {
 	}
 }
 
+type SE []interface{} // slice of empty; notational compactness.
+
+var reorderTests = []struct {
+	fmt string
+	val SE
+	out string
+}{
+	{"%[1]d", SE{1}, "1"},
+	{"%[2]d", SE{2, 1}, "1"},
+	{"%[2]d %[1]d", SE{1, 2}, "2 1"},
+	{"%[2]*[1]d", SE{2, 5}, "    2"},
+	{"%6.2f", SE{12.0}, " 12.00"},
+	{"%[3]*[2].*[1]f", SE{12.0, 2, 6}, " 12.00"},
+	{"%[1]*[2].*[3]f", SE{6, 2, 12.0}, " 12.00"},
+	// An actual use! Print the same arguments twice.
+	{"%d %d %d %#[1]o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015"},
+
+	// Erroneous cases.
+	{"%[]d", SE{2, 1}, "%d(BADARGNUM)"},
+	{"%[-3]d", SE{2, 1}, "%d(BADARGNUM)"},
+	{"%[x]d", SE{2, 1}, "%d(BADARGNUM)"},
+	{"%[23]d", SE{2, 1}, "%d(BADARGNUM)"},
+	{"%[3]", SE{2, 1}, "%!(NOVERB)"},
+	{"%d %d %d %#[1]o %#o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015 %o(MISSING)"},
+}
+
+func TestReorder(t *testing.T) {
+	for _, tt := range reorderTests {
+		s := Sprintf(tt.fmt, tt.val...)
+		if s != tt.out {
+			t.Errorf("Sprintf(%q, %v) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out)
+		} else {
+		}
+	}
+}
+
 func BenchmarkSprintfEmpty(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		Sprintf("")

コアとなるコードの解説

pp 構造体の変更

  • arg interface{}: フォーマットされる現在の引数を保持します。以前のfieldから名称が変更され、より正確な意味合いになりました。
  • reordered bool: フォーマット文字列内で引数の順序が明示的に変更された(%[n]が使用された)場合にtrueになります。これにより、余分な引数のチェックを最適化できます。
  • goodArgNum bool: 最後に解析された引数インデックスが有効であったかどうかを示します。無効なインデックスが指定された場合、エラーメッセージの生成に利用されます。

parseArgNumber(format string) (index int, wid int, ok bool)

この関数は、フォーマット文字列の[から始まる部分を解析し、引数インデックスを抽出します。

  • format[i]]になるまでループし、その間の文字列を数値として解析します。
  • 解析された数値は1から始まるインデックスなので、内部で使用するために1を引いて0から始まるインデックスに変換します。
  • widは、解析された[n]記法がフォーマット文字列内で占めるバイト数を示します。
  • okは、解析が成功したかどうかを示します。

(p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int)

このメソッドは、doPrintf内で呼び出され、次の引数インデックスを決定します。

  • 現在のフォーマット文字列の位置i[が見つかった場合、parseArgNumberを呼び出して明示的な引数インデックスを解析します。
  • 解析が成功し、インデックスが有効な範囲内であれば、p.reorderedtrueに設定し、newArgNumを解析されたインデックスに更新します。
  • 解析が失敗したり、インデックスが範囲外であったりした場合は、p.goodArgNumfalseに設定し、エラー処理に備えます。
  • newiは、フォーマット文字列の次の処理開始位置を返します。

(p *pp) doPrintf(format string, a []interface{}) 内の変更

doPrintfは、fmt.Printf系の関数の中心的なロジックです。

  • ループの開始時にargNum := 0p.reordered = falseが初期化されます。
  • 各フォーマット動詞の処理に入る前に、argNum, i = p.argNumber(argNum, format, i, len(a))が呼び出されます。これにより、%[n]記法が検出された場合、argNumが更新され、次に処理される引数が変更されます。
  • 幅や精度が*で指定される場合(例: %*s)、その*の直後にもp.argNumberが呼び出される可能性があります。これは、%[3]*[2].*[1]fのように、幅や精度自体も引数インデックスで指定されるケースに対応するためです。
  • 引数不足や無効な引数インデックスの場合の新しいエラー処理が追加されました。!p.goodArgNumの場合には%!(BADARGNUM)argNum >= len(a)の場合には%!(MISSING)が出力されます。
  • 最後に、!p.reordered && argNum < len(a)という条件で、余分な引数のチェックが行われます。reorderedtrueの場合は、引数の順序が変更されているため、このチェックはスキップされます。

これらの変更により、fmtパッケージはより柔軟なフォーマット機能を提供し、開発者がより表現力豊かなフォーマット文字列を作成できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (fmtパッケージ)
  • コミットメッセージと差分 (git diff)
  • C言語 printf の引数インデックス指定に関する情報 (POSIX標準)
  • Go言語の国際化に関する一般的な議論