Rustといえどリソースの解放は注意
κeenです。深夜にですが小ネタを。
Rustを使っているとついついリソースの解放のことは忘れてしまうのですが、注意しないといけない類のリソースがあります。
その1つがBufWriter
です。
バッファリングライター全般に言えることですが、奴らはメモリ上にまだ書き込まれてない値を保持しているので解放する前にそれらを掃き出す必要があります。
その時に書き込み例外が起き得るので、解放も安全な処理ではないのです。Javaのtry-with-resource文を使わないファイルの扱いについては悪名高いですが、あれは仕方ない話なのです。
ところでRustのリソースの解放はDrop
トレイトのdrop
が担っていますが、返り値はvoid
です。基本的には裏で動くので当然ですね。
そして、drop
はpanicを起こさないことが望ましいです。Rustは今のところ(1.5時点)panicから回復出来ないのでそれはそうでしょう。では、エラーを返り値でも返せない、panicも起こせない中BufWrite
はどうエラーを扱っているのでしょう。それは実装を見ると分かります。
impl<W: Write> Drop for BufWriter<W> {
fn drop(&mut self) {
if self.inner.is_some() {
// dtors should not panic, so we ignore a failed flush
// 訳: dropはパニックすべきではない、だからflushの失敗は無視する
let _r = self.flush_buf();
}
}
}
…はい。中々アレなことをやってくれますね。
ということでBufWrite
を使う時は
{
let file = File::create("some_file.txt").unwrap();
let br = BufWrite::new(&file);
// do something
match br.flush_buf() {
Ok(()) => (),
Err(e) => // handle errors
}
}
のようにライフタイムの終わりでflush_buf
を呼ぶのが作法的な方法です。
unwrap
と同じく掃き出せない時に無視されてもいいならそこまでする必要はありませんが、それでもスコープの終わりに意図的にflush_buf
を呼んでない旨を書くと丁寧でしょう。
因みにスコープが大きすぎる時は
{
let file = File::create("some_file.txt").unwrap();
{
let br = BufWrite::new(&file);
// do something with br
match br.flush_buf() {
Ok(()) => (),
Err(e) => // handle errors
}
}
// do other things
}
のようにスコープを作って不要なライフタイムを切り詰めるイディオムもあるので併せてどうぞ。
余談ですがstd::fs::File
の実体はCのFILE
構造体ではなくファイルディスクリプタなのでバッファリングはしてません。のでこちらは気にする必要はありません。
という訳で小ネタでした。