Go fmtパッケージの関数Printfの内部実装①
概要
GoのfmtパッケージのPrintf関数で独自に出力のフォーマットを指定したいケースがあったため、
Printf関数でフォーマット処理が具体的にどのように実装されているのかを調べました。
その時の内容を個人の備忘録としてまとめていきます。
なお、記事が長くなってしまうため複数に分けて書いていく予定です。
関数Printf
fmtパッケージのPrintf関数は次のような定義になっています。
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...any) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
コメントの内容を確認するとPrintf関数はformat specifier(verb.書式指定子。例:%s,%vなど)に従って引数で指定された文字列をフォーマットして結果を標準出力に出力する関数とのことです。
この関数の戻り値は書き込まれたbytes数とエラー発生時はそのエラーを返却するとのことです。
Printf関数の具体的な処理内容は 同じくfmtパッケージのFprintf
関数を呼び出して処理を委譲しています。第一引数にos.Stdout
を指定しているのでここで標準出力するように指定していることがわかります。
後続の章で続けて Fprintf
関数の内容について見ていきます。
関数Fprintf
前章でみたように関数Fprintf
は3つの引数をとる関数になっています。
具体的な実装内容は下記になります。
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
コメントの内容を確認するとformat specifier(verb/書式指定子)の値に応じて文字列をフォーマットしてその結果を引数であるio.Writer
のwに書き込みをするようです。
戻り値は実際に書き込んだbytes数とエラーを返却します。
具体的な処理内容について見ていきます。
まず先頭でfmtパッケージ内のプライベートな関数newPrinter()
を呼び出してその結果を変数p
に代入しています。
newPrinter
関数newPrinter
は次のような実装になっています。
構造体pp
をキャッシュがあればそれを、なければ新しくアロケートしてそれを返却します。
// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
構造体pp
の実装は下記です。
コメントにある通り出力用のprinterのステートを保持するための構造体です。
// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
buf buffer
// arg holds the current item, as an interface{}.
arg any
// value is used instead of arg for reflect values.
value reflect.Value
// fmt is used to format basic items such as integers or strings.
fmt fmt
// reordered records whether the format string used argument reordering.
reordered bool
// goodArgNum records whether the most recent reordering directive was valid.
goodArgNum bool
// panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion.
panicking bool
// erroring is set when printing an error string to guard against calling handleMethods.
erroring bool
// wrapErrs is set when the format string may contain a %w verb.
wrapErrs bool
// wrappedErr records the target of the %w verb.
wrappedErr error
}
各フィールドのコメントを確認していきます。
-
buf
:フォーマットを結果を書き込むbufferです -
arg
:現在のitemをinterface型で保持します -
value
: フィールドargの代わりとしてreflect values用に利用します -
fmt
: integerやstringなど基本型のitemをフォーマットする際に利用されます -
reordered
:フォーマット文字列が引数の並び替えを使用したかどうかを記録します -
goodArgNum
:もっとも直近のreordering directiveがvalidかどうかを記録します -
panicking
:catchPanicによって無限panicやrecover,panic, 再帰を避けるためにセットされます -
erroring
: erroring は、handleMethods の呼び出しを防ぐためにエラー文字列を出力するときに設定されます -
wrapErrs
: format文字列に%w
verbが含まれているかどうかを保持します -
wrappedErr
: verb%w
のターゲットとなるerrorを記録します
各フィールドの詳細は後続の実装で実際に利用する場面で確認していきます。
関数newPrinter()
では、構造体pp
のフィールドpanicking
,erroring
,wrapErrs
のそれぞれにfalse
を指定して返却しています。
newPrinter
のおよその実装内容は以上になります。
newPrinter
呼び出し元のの実装について再びみていきます。
func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
p := newPrinter() // ←確認済み
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
2行目でnewPrinter
で生成した構造体pp
のプライベートメソッドdoPrintf
を呼び出しています。
メソッドdoPrintf
doPrintf
は下記のように175行もある長いメソッドになっています。順に少しずつ見ていきます。
func (p *pp) doPrintf(format string, a []any) {
end := len(format)
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
p.reordered = false
formatLoop:
for i := 0; i < end; {
p.goodArgNum = true
lasti := i
for i < end && format[i] != '%' {
i++
}
if i > lasti {
p.buf.writeString(format[lasti:i])
}
if i >= end {
// done processing format string
break
}
// Process one verb
i++
// Do we have flags?
p.fmt.clearflags()
simpleFormat:
for ; i < end; i++ {
c := format[i]
switch c {
case '#':
p.fmt.sharp = true
case '0':
p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
case '+':
p.fmt.plus = true
case '-':
p.fmt.minus = true
p.fmt.zero = false // Do not pad with zeros to the right.
case ' ':
p.fmt.space = true
default:
// Fast path for common case of ascii lower case simple verbs
// without precision or width or argument indices.
if 'a' <= c && c <= 'z' && argNum < len(a) {
if c == 'v' {
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
// Struct-field syntax
p.fmt.plusV = p.fmt.plus
p.fmt.plus = false
}
p.printArg(a[argNum], rune(c))
argNum++
i++
continue formatLoop
}
// Format is more complex than simple flags and a verb or is malformed.
break simpleFormat
}
}
// Do we have an explicit argument index?
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
// Do we have width?
if i < end && format[i] == '*' {
i++
p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum)
if !p.fmt.widPresent {
p.buf.writeString(badWidthString)
}
// We have a negative width, so take its value and ensure
// that the minus flag is set
if p.fmt.wid < 0 {
p.fmt.wid = -p.fmt.wid
p.fmt.minus = true
p.fmt.zero = false // Do not pad with zeros to the right.
}
afterIndex = false
} else {
p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end)
if afterIndex && p.fmt.widPresent { // "%[3]2d"
p.goodArgNum = false
}
}
// Do we have precision?
if i+1 < end && format[i] == '.' {
i++
if afterIndex { // "%[3].2d"
p.goodArgNum = false
}
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
if i < end && format[i] == '*' {
i++
p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum)
// Negative precision arguments don't make sense
if p.fmt.prec < 0 {
p.fmt.prec = 0
p.fmt.precPresent = false
}
if !p.fmt.precPresent {
p.buf.writeString(badPrecString)
}
afterIndex = false
} else {
p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end)
if !p.fmt.precPresent {
p.fmt.prec = 0
p.fmt.precPresent = true
}
}
}
if !afterIndex {
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
}
if i >= end {
p.buf.writeString(noVerbString)
break
}
verb, size := rune(format[i]), 1
if verb >= utf8.RuneSelf {
verb, size = utf8.DecodeRuneInString(format[i:])
}
i += size
switch {
case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec.
p.buf.writeByte('%')
case !p.goodArgNum:
p.badArgNum(verb)
case argNum >= len(a): // No argument left over to print for the current verb.
p.missingArg(verb)
case verb == 'v':
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
// Struct-field syntax
p.fmt.plusV = p.fmt.plus
p.fmt.plus = false
fallthrough
default:
p.printArg(a[argNum], verb)
argNum++
}
}
// Check for extra arguments unless the call accessed the arguments
// out of order, in which case it's too expensive to detect if they've all
// been used and arguably OK if they're not.
if !p.reordered && argNum < len(a) {
p.fmt.clearflags()
p.buf.writeString(extraString)
for i, arg := range a[argNum:] {
if i > 0 {
p.buf.writeString(commaSpaceString)
}
if arg == nil {
p.buf.writeString(nilAngleString)
} else {
p.buf.writeString(reflect.TypeOf(arg).String())
p.buf.writeByte('=')
p.printArg(arg, 'v')
}
}
p.buf.writeByte(')')
}
}
第一引数format
はstring型でverbが指定されます。例えば%+v
や%s
などです。
第二引数のany型の配列 a
はフォーマット対象の文字列になります。
関数冒頭の1~4行目で処理に必要な前処理を行なっています。
変数end
は引数format
の文字数を代入しています。
func (p *pp) doPrintf(format string, a []any) {
end := len(format)
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
p.reordered = false
続くラベルformatLoop
以降のfor文がメインの処理になっています。
for i := 0; i < end; {
p.goodArgNum = true
lasti := i
for i < end && format[i] != '%' {
i++
}
if i > lasti {
p.buf.writeString(format[lasti:i])
}
if i >= end {
// done processing format string
break
}
前半部分でイテレーターi
の値とフォーマット文字列の文字数との比較やイテレートで処理対象のフォーマット文字が'%'なのかどうかで処理を出し分けています。
イテレーターi
が変数lasti
より大きい場合、つまりの場合なので
メソッドwriteString
を呼び出して
i
がフォーマット文字列数以上の場合forのループ処理をbreakします。
続けて後続の処理を見ていきます。
// Process one verb
i++
// Do we have flags?
p.fmt.clearflags()
イテレーターi
をインクリメントして、続けて clearflags()
でフラグをクリアしています。
続けて後続の処理を見ていきます。こちらがメソッドdoPrintf
のメイン処理になっています。
simpleFormat:
for ; i < end; i++ {
c := format[i]
switch c {
case '#':
p.fmt.sharp = true
case '0':
p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
case '+':
p.fmt.plus = true
case '-':
p.fmt.minus = true
p.fmt.zero = false // Do not pad with zeros to the right.
case ' ':
p.fmt.space = true
default:
// Fast path for common case of ascii lower case simple verbs
// without precision or width or argument indices.
if 'a' <= c && c <= 'z' && argNum < len(a) {
if c == 'v' {
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
// Struct-field syntax
p.fmt.plusV = p.fmt.plus
p.fmt.plus = false
}
p.printArg(a[argNum], rune(c))
argNum++
i++
continue formatLoop
}
// Format is more complex than simple flags and a verb or is malformed.
break simpleFormat
}
}
ここではフォーマット文字の値に応じてフォーマットに必要な前処理を実行しています。
具体的には、フォーマット文字がラベルsimpleFormat
の場合(後続で確認します)、フォーマット文字の値に応じて具体的なフォーマットの処理で利用する構造体pp
のフィールドfmt
の各該当する値をセットしています。
前の章の構造体pp
でみたようにフィールドfmt
はフォーマット文字列がシンプルフォーマットの場合のフォーマット処理に利用されるフィールドになっています。
▼構造体pp
のフィールドfmt
// A fmt is the raw formatter used by Printf etc.
// It prints into a buffer that must be set up separately.
type fmt struct {
buf *buffer
fmtFlags
wid int // width
prec int // precision
// intbuf is large enough to store %b of an int64 with a sign and
// avoids padding at the end of the struct on 32 bit architectures.
intbuf [68]byte
}
例えばイテレート要素のフォーマット文字が+
の場合、fmt
のフィールドfmtFlags
のフィールドplus
にtrue
をセットします。
simpleFormat
のその他の例としては#
, 0
,-
,
が列挙されています。
それ以外のフォーマット文字の場合はswitchのdefaultブロックが実行されます。
default:
// Fast path for common case of ascii lower case simple verbs
// without precision or width or argument indices.
if 'a' <= c && c <= 'z' && argNum < len(a) {
if c == 'v' {
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
// Struct-field syntax
p.fmt.plusV = p.fmt.plus
p.fmt.plus = false
}
p.printArg(a[argNum], rune(c))
argNum++
i++
continue formatLoop
}
// Format is more complex than simple flags and a verb or is malformed.
break simpleFormat
}
アスキーの小文字のシンプルなverbの場合かどうかをここでもチェックしています。
もし対象フォーマット文字がv
の場合fmt
のverbv
のフォーマットに利用する各フィールドに必要な値をセットしています。
その後構造体pp
のメソッドprintArg
を呼び出しています(後続で見ていきます)
続けて argNum
、いてレーターのi
をそれぞれインクリメントして、ラベルformatLoop
の処理を継続します。
それ以外の場合はsimpleFormat
ではないとみなしてラベルsimpleFormat
の処理をbreakします。
続けてprintArg
メソッドの内容をみていきます。
printArg
printArg
の実装は下記になります。
83行ほどの長いプライベートメソッドです。
func (p *pp) printArg(arg any, verb rune) {
p.arg = arg
p.value = reflect.Value{}
if arg == nil {
switch verb {
case 'T', 'v':
p.fmt.padString(nilAngleString)
default:
p.badVerb(verb)
}
return
}
// Special processing considerations.
// %T (the value's type) and %p (its address) are special; we always do them first.
switch verb {
case 'T':
p.fmt.fmtS(reflect.TypeOf(arg).String())
return
case 'p':
p.fmtPointer(reflect.ValueOf(arg), 'p')
return
}
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex(complex128(f), 64, verb)
case complex128:
p.fmtComplex(f, 128, verb)
case int:
p.fmtInteger(uint64(f), signed, verb)
case int8:
p.fmtInteger(uint64(f), signed, verb)
case int16:
p.fmtInteger(uint64(f), signed, verb)
case int32:
p.fmtInteger(uint64(f), signed, verb)
case int64:
p.fmtInteger(uint64(f), signed, verb)
case uint:
p.fmtInteger(uint64(f), unsigned, verb)
case uint8:
p.fmtInteger(uint64(f), unsigned, verb)
case uint16:
p.fmtInteger(uint64(f), unsigned, verb)
case uint32:
p.fmtInteger(uint64(f), unsigned, verb)
case uint64:
p.fmtInteger(f, unsigned, verb)
case uintptr:
p.fmtInteger(uint64(f), unsigned, verb)
case string:
p.fmtString(f, verb)
case []byte:
p.fmtBytes(f, verb, "[]byte")
case reflect.Value:
// Handle extractable values with special methods
// since printValue does not handle them at depth 0.
if f.IsValid() && f.CanInterface() {
p.arg = f.Interface()
if p.handleMethods(verb) {
return
}
}
p.printValue(f, verb, 0)
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
第一引数arg
はprintArg
呼び出し側のa[argNum]
とあるように、format対象のPrintfで出力する内容になります。
例えば次のようにPrintf
が呼び出された場合、a[0]
がUser{ID: 1, Name: "Taro"}
になります。
fmt.Printf("%+v", User{ID: 1, Name: "Taro"})
第二引数のrune型のverb
はその名の通りverb/書式指定子を受け取ります。
メソッドprintArg
の冒頭で引数argの値がnilの場合の処理を定義しています。
p.arg = arg
p.value = reflect.Value{}
if arg == nil {
switch verb {
case 'T', 'v':
p.fmt.padString(nilAngleString)
default:
p.badVerb(verb)
}
return
}
続けて
// Special processing considerations.
// %T (the value's type) and %p (its address) are special; we always do them first.
switch verb {
case 'T':
p.fmt.fmtS(reflect.TypeOf(arg).String())
return
case 'p':
p.fmtPointer(reflect.ValueOf(arg), 'p')
return
}
引数verb
が特別な考慮が必要なケースの処理を定義しています。
具体的にはT
やp
などの場合です。今回は基本的な部分についてみていくのでこの処理はスキップします。
引数arg
の型に応じてswitch
で処理を条件分岐しています。
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
...
もし型がbool
の場合はbool型としてフォーマットします。
func (p *pp) fmtBool(v bool, verb rune) {
switch verb {
case 't', 'v':
p.fmt.fmtBoolean(v)
default:
p.badVerb(verb)
}
}
// fmtBoolean formats a boolean.
func (f *fmt) fmtBoolean(v bool) {
if v {
f.padString("true")
} else {
f.padString("false")
}
}
それ以外の型の場合にswitchのdefault
ブロックで処理されます。
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
コメントにある通り、出力対象の値の型がシンプルでない場合、フォーマット用の独自のメソッドが定義されているケースを想定してメソッドhandleMethods
が呼び出されています。(後続で確認します)
もしこのhandleMethod
でもフォーマット処理が対応できない場合(戻り値がfalse
の場合)、printValue
メソッドを呼びます。
Printf関数に渡した引数が独自にフォーマット用のメソッドを実装している場合は
メソッドhandleMethods
で処理されることになります。
それではこのメソッドの実装を見ていきます。
func (p *pp) handleMethods(verb rune) (handled bool) {
if p.erroring {
return
}
if verb == 'w' {
// It is invalid to use %w other than with Errorf, more than once,
// or with a non-error arg.
err, ok := p.arg.(error)
if !ok || !p.wrapErrs || p.wrappedErr != nil {
p.wrappedErr = nil
p.wrapErrs = false
p.badVerb(verb)
return true
}
p.wrappedErr = err
// If the arg is a Formatter, pass 'v' as the verb to it.
verb = 'v'
}
// Is it a Formatter?
if formatter, ok := p.arg.(Formatter); ok {
handled = true
defer p.catchPanic(p.arg, verb, "Format")
formatter.Format(p, verb)
return
}
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
if p.fmt.sharpV {
if stringer, ok := p.arg.(GoStringer); ok {
handled = true
defer p.catchPanic(p.arg, verb, "GoString")
// Print the result of GoString unadorned.
p.fmt.fmtS(stringer.GoString())
return
}
} else {
// If a string is acceptable according to the format, see if
// the value satisfies one of the string-valued interfaces.
// Println etc. set verb to %v, which is "stringable".
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting handled and deferring catchPanic
// must happen before calling the method.
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
}
}
return false
}
関数の冒頭でerroring
がtrue
の場合は早期returnsしています。
続けてverbがw
、つまりエラー用のフォーマットの場合の処理が定義されています。
func (p *pp) handleMethods(verb rune) (handled bool) {
if p.erroring {
return
}
if verb == 'w' {
// It is invalid to use %w other than with Errorf, more than once,
// or with a non-error arg.
err, ok := p.arg.(error)
if !ok || !p.wrapErrs || p.wrappedErr != nil {
p.wrappedErr = nil
p.wrapErrs = false
p.badVerb(verb)
return true
}
p.wrappedErr = err
// If the arg is a Formatter, pass 'v' as the verb to it.
verb = 'v'
}
続けて引数arg
がFormatter
型に型変換可能かどうかを判定しています。
Formatterインターフェース
詳しくは今回は長くなってしまうので別記事で見ていきますが、
このFormatter
はinterfaceになっていて、具体的にはFormat
メソッドを定めたinterfaceになっています。もし独自のフォーマットで出力したい場合などはこのinterfaceを満たすように実装することでそのメソッドで定義した内容でフォーマット出力することができます。
formatter.Format(p, verb)
の部分で具体的にメソッドFormat
を呼び出しています。
// Is it a Formatter?
if formatter, ok := p.arg.(Formatter); ok {
handled = true
defer p.catchPanic(p.arg, verb, "Format")
formatter.Format(p, verb)
return
}
GoStringerインタフェース
続けて、Formatterインタフェースを満たしてない場合、出力対象がGo syntaxに即していて引数arg
が処理可能な場合はここで処理されます。
具体的にはstringer
インターフェースのメソッドGoString()
を実装している場合はそのメソッドを呼び出します。
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
if p.fmt.sharpV {
if stringer, ok := p.arg.(GoStringer); ok {
handled = true
defer p.catchPanic(p.arg, verb, "GoString")
// Print the result of GoString unadorned.
p.fmt.fmtS(stringer.GoString())
return
}
}
そうでない場合は次のようにverbの値に応じてフォーマット内容を出し分けています。
} else {
// If a string is acceptable according to the format, see if
// the value satisfies one of the string-valued interfaces.
// Println etc. set verb to %v, which is "stringable".
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting handled and deferring catchPanic
// must happen before calling the method.
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)
return
}
}
}
メソッドhandleMethods
の処理内容は以上になります。
メソッドdoPrintf
の処理の途中ですが今回の記事は長くなってしまうので一旦今回はここまでとします。
最後にdoPrintf関数のここまで見てきた内容の部分を再掲します。
func (p *pp) doPrintf(format string, a []any) {
end := len(format)
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
p.reordered = false
formatLoop:
for i := 0; i < end; {
p.goodArgNum = true
lasti := i
for i < end && format[i] != '%' {
i++
}
if i > lasti {
p.buf.writeString(format[lasti:i])
}
if i >= end {
// done processing format string
break
}
// Process one verb
i++
// Do we have flags?
p.fmt.clearflags()
simpleFormat:
for ; i < end; i++ {
c := format[i]
switch c {
case '#':
p.fmt.sharp = true
case '0':
p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
case '+':
p.fmt.plus = true
case '-':
p.fmt.minus = true
p.fmt.zero = false // Do not pad with zeros to the right.
case ' ':
p.fmt.space = true
default:
// Fast path for common case of ascii lower case simple verbs
// without precision or width or argument indices.
if 'a' <= c && c <= 'z' && argNum < len(a) {
if c == 'v' {
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
// Struct-field syntax
p.fmt.plusV = p.fmt.plus
p.fmt.plus = false
}
p.printArg(a[argNum], rune(c))
argNum++
i++
continue formatLoop
}
// Format is more complex than simple flags and a verb or is malformed.
break simpleFormat
}
}
... 次回はこの行以降を確認していきます
// Do we have an explicit argument index?
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
...
}
}
さらにdoPrintf
の呼び出し元の関数も再掲して終わりにします。
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
Discussion