はじめに
元ネタはtenntennさんの伝説カンファレンスの一部になります。
https://www.youtube.com/watch?v=X0tzCL5gqr8
コードリーディング会とは何人かで同じコードを各々で読んで、発見を共有して理解を深めましょう!というのが趣旨と理解しています。しかし知人で一緒にGoを学んでくれる人も見つけられず、悲しくも一人でコードリーディング会を開催することになりました。
やってみたら読めない箇所が多すぎました。1文ずつ嚙み砕いてGo言語の理解を深めていく作業になりました。その過程の不明点と解釈を記事にして公表していこうと思います。僕と同じ程度の理解度(初学者)の方が、不明点を理解するきっかけになればうれしいです。
※1日の学びを1記事にするので情報量は少なめです。徐々に情報量を増やして有益な記事を書けるよう頑張ります。
※個人の解釈を投稿する記事なので誤った情報が多々あると思います。申し訳ありませんがご了承ください。誤りについてはご指摘いただけるとうれしいです。
本記事は 3回目となり、 fmt
パッケージを読み進めます。
※前回まではerrors
パッケージを読み進めていましたが、少し脱線します。
メモ
関数Printf
は別の関数 Fprintf
を呼んでいるだけ
以下の通りです。
// 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 ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
何気なく使っていた fmt.Printf
ですが別の関数を呼んでいるだけなんですね。。。。 Printf
は Fprintf
をラッパーしているという理解です。
しかも Printf
の引数に注目すると、第一引数にstring型、第二引数以降はインタフェースで受け取っていることがわかります。 Printf
を利用するときは当たり前に文字列や数値を好き勝手渡していましたが、インタフェースで受け取っているから実行可能だとわかりました。
os.Stdout
はosパッケージの関数Stdout
ではない
Fprintf
の引数で渡している os.Stdout
について、「標準出力に文字列を返すおまじない。多分関数とかでしょ。」と認識していたので、osパッケージに以下のような関数があると思ってました。
//※以下のような関数はありません!!
func Stdout(str string) string {
しかし実際は以下のように Stdot
は変数でした。
// Note that the Go runtime writes to standard error for panics and crashes;
// closing Stderr may cause those messages to go elsewhere, perhaps
// to a file opened later.
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
以下のように書かれています。
Stdin, Stdout, and Stderr are open Files pointing to the standard input, standard output, and standard error file descriptors.
(Stdin, Stdout, Stderr はオープン 標準入力、標準出力、標準エラーファイル記述子を指すファイルです。)
そうらしいです。標準出力のファイルを渡しているのか?となってくると沼な気がするのでこの辺で辞めますが、 os.Stdout
は関数ではなく、関数 NewFile
で作成された標準出力をするために作成されたファイルだという点は理解できました。では引き続き Fprintf
を確認していきます。
newPrinter
は構造体 pp
のポインタを返している
Fprintf
の処理を開始してすぐ newPrinter
を呼んでいたのでそちらを確認しに行きます。
// 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 ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
ここで newPrinter
が *pp
型の何かを返しているとわかりました。次は *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
は構造体でした。
// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
コメント部分を和訳します。
pp はプリンタの状態を保存するために使用され、割り当てを避けるために sync.Pool で再利用されます。
正直よくわかりません。ですが Fprintf
の2行目が pp
型のポインタレシーバを持つ doPrintf
メソッドを実行していることがわかりました。(数日前はこの処理まで追えなかったはずなので、少しは成長したかも。「プログラミング言語Go」すごい。)詳しい処理は追いませんが、レシーバを利用して渡した引数(構造体のフィールド)を更新してるはず。。
で最後に free
メソッドを呼んでreturnしていますね。
// free saves used pp structs in ppFree; avoids an allocation per invocation.
func (p *pp) free() {
// Proper usage of a sync.Pool requires each entry to have approximately
// the same memory cost. To obtain this property when the stored type
// contains a variably-sized buffer, we add a hard limit on the maximum buffer
// to place back in the pool.
//
// See https://golang.org/issue/23199
if cap(p.buf) > 64<<10 {
return
}
free saves used pp structs in ppFree; avoids an allocation per invocation.
(free は、ppFree に使用された pp 構造体を保存します;呼び出しごとのアロケーションを回避します。)
どこかのコメントでキャッシュされた pp
がある場合はそれを使う意図が書いてあったコメント見た。これですね。
// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
p := ppFree.Get().(*pp)
ppFree.Get().(*pp)
これなんだ?
.(*pp)
の箇所は型アサーションと呼ばれるインタフェース関連のやつです。まだ勉強できてないので本日のコードリーディングでは飛ばしますが、課題として残しておきます。
var ppFree = sync.Pool{
New: func() interface{} { return new(pp) },
}
↑これも次回の課題ってことで。ぜんぜんわからん。
最後に
少しだけ読めた気がしました。明日は休日(振休)なのでインターフェースの理解を深めたいと思います。
余談
明日は1Go日(15日)ですね。