実質Futureさんの記事の引用なんだけれど自分用メモ。
TL;DR
- Go1.16で
io/ioutil#ReadAll関数がio#ReadAll関数に書き直された io#ReadAll関数はbytes#Buffer型を利用せずにバッファ管理を行なっているb = append(b, 0)[:len(b)]
appendを使ったDRYなコード。appendの性能向上や最適化の恩恵に預かれるすごいコード- ただ自分で使う機会はあまりないかもしれない
Go1.16で追加されたio#ReadAll
先日リリースされたGo1.16ではio/ioutil#ReadAll関数がio#ReadAll関数に書き直された。
- Go 1.16 is released - The Go Blog
ReadAll関数はio.Readerインターフェイスからすべてのデータを読み出す関数だ。
func ReadAll(r Reader) ([]byte, error)
移植されたReadAll関数は内部の実装がすべて書き換えられている。
Go1.15以前のio/ioutil#ReadAll関数はbytesパッケージに依存していたが、bytesパッケージはioパッケージをimportしており循環参照になるため利用できないからだ。
https://github.com/golang/go/blob/go1.16/src/bytes/buffer.go#L9-L13
// bytes/buffer.go の冒頭
import (
"errors"
"io"
"unicode/utf8"
)
io/ioutil#ReadAllの実装
@rscさんによって追加されたCLは次のCLになる。
該当コードはこちら。
https://github.com/golang/go/blob/go1.16/src/io/io.go#L622-L642
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r Reader) ([]byte, error) {
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)]
}
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}
この中のb = append(b, 0)[:len(b)]が凄まじいなと思った。
append関数を利用してバッファを拡張する
最初読んだとき何しているのかわからなかったが、ここでやっているのは次の処理だ。
- 1バイトだけ追加する
append関数を実行する- 直前の
len(b) == cap(b)より、バッファはキャパシティ不足
- 直前の
- バッファのキャパシティが
append関数によって拡張され0が追加される 0がひとつだけ増えたバッファだが[:len(b)]によって元の長さのスライスになる。
以下の点で「さすがすぎる…」という感想を持った。
- たった一行のスマートなコード
- ポインタいじったりカーネルに近い操作をしているわけではないシンプルなコード
- パット見よくわからないがちゃんと解説コメントが書いてある
- DRY
append関数に対するパフォーマンスチューニングや最適化に依存できる
一度みたら書けるが自分ではこのやり方は思いつかないなと思った。
終わりに
同様な状況を自分で実装するとき、大抵はbytes#Buffer型を利用しているか確保すべきバッファサイズが自明だろう。
よってなかなか独自型でストリームデータを扱うことはないかもしれないが、なにかあったときはスッと実装したいイディオムだった。
というかGo1.16全然触れていないので早く触らなければ!
余談
標準パッケージもpkg.go.devで見るようになった認識だ。 しかしpkg.go.devだと追加されたバージョンがわからないので、まだgolang.org/pkg見ていたほうがいいのかな?