LoginSignup
0
0

More than 1 year has passed since last update.

# 【Go】一人コードリーディング会を始めます。(3回目)

Posted at

はじめに

元ネタは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 ですが別の関数を呼んでいるだけなんですね。。。。 PrintfFprintf をラッパーしているという理解です。
しかも 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日)ですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0