🚛

Go fmtパッケージの関数Printfの内部実装①

2022/12/31に公開約22,400字

概要

GoのfmtパッケージのPrintf関数で独自に出力のフォーマットを指定したいケースがあったため、
Printf関数でフォーマット処理が具体的にどのように実装されているのかを調べました。
その時の内容を個人の備忘録としてまとめていきます。

なお、記事が長くなってしまうため複数に分けて書いていく予定です。

関数Printf

fmtパッケージのPrintf関数は次のような定義になっています。

go/src/fmt/print.go#L210
// 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...)
}

https://pkg.go.dev/fmt#Printf

コメントの内容を確認するとPrintf関数はformat specifier(verb.書式指定子。例:%s,%vなど)に従って引数で指定された文字列をフォーマットして結果を標準出力に出力する関数とのことです。

この関数の戻り値は書き込まれたbytes数とエラー発生時はそのエラーを返却するとのことです。

Printf関数の具体的な処理内容は 同じくfmtパッケージのFprintf関数を呼び出して処理を委譲しています。第一引数にos.Stdoutを指定しているのでここで標準出力するように指定していることがわかります。

後続の章で続けて Fprintf関数の内容について見ていきます。

関数Fprintf

前章でみたように関数Fprintfは3つの引数をとる関数になっています。
具体的な実装内容は下記になります。

go/src/fmt/print.go#L200
// 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数とエラーを返却します。

https://pkg.go.dev/fmt#Fprintf

具体的な処理内容について見ていきます。

まず先頭でfmtパッケージ内のプライベートな関数newPrinter()を呼び出してその結果を変数pに代入しています。

newPrinter

関数newPrinterは次のような実装になっています。
構造体ppをキャッシュがあればそれを、なければ新しくアロケートしてそれを返却します。

go/src/fmt/print.go#L135
// 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のステートを保持するための構造体です。

go/src/fmt/print.go#L104
// 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文字列に%wverbが含まれているかどうかを保持します
  • wrappedErr: verb%wのターゲットとなるerrorを記録します

各フィールドの詳細は後続の実装で実際に利用する場面で確認していきます。

関数newPrinter()では、構造体ppのフィールドpanicking,erroring,wrapErrsのそれぞれにfalseを指定して返却しています。

newPrinterのおよその実装内容は以上になります。
newPrinter呼び出し元のの実装について再びみていきます。

go/src/fmt/print.go#L202
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行もある長いメソッドになっています。順に少しずつ見ていきます。

go/src/fmt/print.go#L1005
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文がメインの処理になっています。

go/src/fmt/print.go#L1011
	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します。

続けて後続の処理を見ていきます。

go/src/fmt/print.go#L1025
		// Process one verb
		i++

		// Do we have flags?
		p.fmt.clearflags()

イテレーターiをインクリメントして、続けて clearflags()でフラグをクリアしています。

続けて後続の処理を見ていきます。こちらがメソッドdoPrintfのメイン処理になっています。

go/src/fmt/print.go#L1030
	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

go/src/fmt/format.go#L39
// 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のフィールドplustrueをセットします。
simpleFormatのその他の例としては#, 0,-, が列挙されています。

それ以外のフォーマット文字の場合はswitchのdefaultブロックが実行されます。

go/src/fmt/print.go#L1045
			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行ほどの長いプライベートメソッドです。

go/src/fmt/print.go#665
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)
		}
	}
}

第一引数argprintArg呼び出し側の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の場合の処理を定義しています。

go/src/fmt/print.go#666
	p.arg = arg
	p.value = reflect.Value{}

	if arg == nil {
		switch verb {
		case 'T', 'v':
			p.fmt.padString(nilAngleString)
		default:
			p.badVerb(verb)
		}
		return
	}

続けて

go/src/fmt/print.go#679
	// 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が特別な考慮が必要なケースの処理を定義しています。
具体的にはTpなどの場合です。今回は基本的な部分についてみていくのでこの処理はスキップします。

引数argの型に応じてswitchで処理を条件分岐しています。

go/src/fmt/print.go#690
	// 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型としてフォーマットします。

go/src/fmt/print.go#382
func (p *pp) fmtBool(v bool, verb rune) {
	switch verb {
	case 't', 'v':
		p.fmt.fmtBoolean(v)
	default:
		p.badVerb(verb)
	}
}
go/src/fmt/format.go#125
// fmtBoolean formats a boolean.
func (f *fmt) fmtBoolean(v bool) {
	if v {
		f.padString("true")
	} else {
		f.padString("false")
	}
}

それ以外の型の場合にswitchのdefaultブロックで処理されます。

go/src/fmt/print.go#738
	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で処理されることになります。

それではこのメソッドの実装を見ていきます。

go/src/fmt/print.go#L601
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
}

関数の冒頭でerroringtrueの場合は早期returnsしています。
続けてverbがw、つまりエラー用のフォーマットの場合の処理が定義されています。

go/src/fmt/print.go#L601
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'
	}

続けて引数argFormatter型に型変換可能かどうかを判定しています。

Formatterインターフェース

詳しくは今回は長くなってしまうので別記事で見ていきますが、
このFormatterはinterfaceになっていて、具体的にはFormatメソッドを定めたinterfaceになっています。もし独自のフォーマットで出力したい場合などはこのinterfaceを満たすように実装することでそのメソッドで定義した内容でフォーマット出力することができます。

https://pkg.go.dev/fmt#Formatter

formatter.Format(p, verb)の部分で具体的にメソッドFormatを呼び出しています。

go/src/fmt/print.go#L620
	// 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()を実装している場合はそのメソッドを呼び出します。

https://pkg.go.dev/fmt#GoStringer

go/src/fmt/print.go#L628
	// 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の値に応じてフォーマット内容を出し分けています。

go/src/fmt/print.go#L637
	} 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関数のここまで見てきた内容の部分を再掲します。

go/src/fmt/print.go#L1005
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の呼び出し元の関数も再掲して終わりにします。

go/src/fmt/print.go#L201
// 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

ログインするとコメントできます