Rustでエラーが出てないのにファイルに書き出せないときは

κeenです。随分前から書こうと思いつつ先送りになっていた小ネタです。

例えばカレントディレクトリにあるsome_file.txtに適当なデータを書き込もうとして、以下のようなコードを書いたとします。

use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;

fn main() {
    let file = File::open("some_file.txt").unwrap();
    let mut w = BufWriter::new(file);
    // unwrapを呼んで書き込みエラーを検知
    write!(w, "hello").unwrap();
}

これを実行してみましょう。

$ rustc write_file.rs
$ ./write_file

特段エラーは出ません。しかしながらsome_file.txtの中身は特に書き変わっていません。

$ cat some_file.txt
$

これ、パッと原因分かりますか?

直接の原因はFile::openです。File::openはリードオンリーでファイルを開くのでFile::openで開いたファイルに書き込もうとしても書き込めません(書き込みたいならFile::createを使います)。 じゃあなぜエラーが出ないかというとBufWriterのせいです。 書き込んだ文字列"hello"は短いのでwrite!を発効した時点ではまだデータはバッファに書き込まれるだけです。 このときにはまだエラーは出ません。 そしてmainの末尾でwのライフタイムが終わるときにBufWritedropが呼ばれますが、ここではエラーが無視されるのでユーザにはエラーが起きてないように見える訳です。

このような事故を防ぐために以下のようにflushを呼びましょう。

use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;

fn main() {
    let file = File::open("some_file.txt").unwrap();
    let mut w = BufWriter::new(file);
    // unwrapを呼んで書き込みエラーを検知
    write!(w, "hello").unwrap();
    // flushを呼ぶことで書き込みエラーを全て拾える
    w.flush().unwrap();
}
$ rustc write_file.rs
$ ./write_file
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { repr: Os { code: 9, message: "Bad file descriptor" } }', /checkout/src/libcore/result.rs:859
note: Run with `RUST_BACKTRACE=1` for a backtrace.

参考: Rustといえどリソースの解放は注意 | κeenのHappy Hacκing Blog

Written by κeen