GO言語の便利な機能に defer
があります。
共通の後処理を1つにすることが出来るので、ちょっとこの言語を齧り出すと、何も考えずに片っ端から、
a.Open()
defer a.Close()
b.Open()
defer b.Close()
という書き方をするようになります。
大抵の場合はこれで問題ないのですが、compress下のパッケージに罠が仕掛けられており、ワタシのように、何も考えずに使うと見事に引っかかります。
下記が、迂闊に何も考えずに書いたコードです。
import (
// ...
"compress/gzip" // compress/zlib等でも同様
// ...
)
func compress(data string) ([]byte, error) {
b := new(bytes.Buffer)
gz := gzip.NewWriter(b)
defer gz.Close() // これ
if _, err := gz.Write([]byte(data)); err != nil {
return nil, err
}
if err := gz.Flush(); err != nil {
return nil, err
}
return b.Bytes(), nil
}
上記のコードの場合、
- gz.Write()
- gzFlush()
- b.Bytes()
- gz.Close()
の順に処理されます。
Flushを呼んでいるので、一見問題ないように思うのですが、このcompress下のパッケージ、Close時に最終的なデータを確定します。なので、値が確定する前に値を返してしまい、それが圧縮後のデータと思い込んで保存すると、解凍する際に、 “unexpected EOF” エラーという地獄が発生します。
地獄に落ちずに畜生界に留まり続けるためには、下記のように、しっかりCloseする必要があります。
func compress(data string) ([]byte, error) {
b := new(bytes.Buffer)
gz := gzip.NewWriter(b)
if _, err := gz.Write([]byte(data)); err != nil {
gz.Close()
return nil, err
}
if err := gz.Flush(); err != nil {
gz.Close()
return nil, err
}
gz.Close() // これ
return b.Bytes(), nil
}