備忘録 blog

Docker/Machine Learning/Linux

Rust で Write Offset を取得したい

tl;dr

Rust で、Write + Seek トレイトを実装したストリームにバイナリを書き出していくときに、いまどれだけ書き出したのかが分かっているとする。このとき 書き始めた位置の Offset を持っておくと、その offset 位置から読み出したり、逆にその位置からデータを上書きして記録するために、ランダムアクセスすることができるようになるということで、利点がある。ここでは、そのやり方を検討する。

例えば、好きな位置からWrite するのは、このライブラリが利用可能である。

https://docs.rs/positioned-io/0.2.2/positioned_io/

では、どの位置からWrite すべきかを、書き出すときに index として持っておくには、どうしたら良いだろうか。

まず現在は、バイナリ列を出力するために、byteorder というLittleEndian/BigEndian を切り替えることができる library を利用して整数を出力しているほか、文字列はu8 のベクトルとして、 std::io の write_all で出力しているとする。このとき、write offset を記録する方法として、調べた限り、2つの方法が有力であるようにみえる。

  1. 今書き出した位置と、 Seek:0 からの距離を計算する。
  2. Write trait を継承した struct を作り、それは write の戻り値を返すことで、何byte 書き込んだかを保持しておくことができる。

それぞれの方法について、概説する。

1. 今書き出した位置と、 Seek:0 からの距離を毎回取得するようにする

https://stackoverflow.com/questions/42187591/how-to-keep-track-of-how-many-bytes-written-when-using-stdiowrite

Write + Seek trait を実装したWriter に書き出していくとき、そのたびに今書き出した位置と、seek:0 からの距離を計算すると、それが今書き込んだ領域の逆算となる。

https://doc.rust-lang.org/std/io/trait.Seek.html

    let new_position = file.seek(SeekFrom::Start(any_offset)).unwrap();
    println!("{:?}", new_position);

これの派生版として、 Cursor を用いるケースもある。これは、上述のコードの代わりに以下のように取得できる。

    let mut cursor = Cursor::new(contents);
    cursor.seek(SeekFrom::Start(any_offset));
    println!("{:?}", cursor.position());

https://stackoverflow.com/questions/34878970/how-to-get-current-cursor-position-in-file/34888248#34888248

うまく工夫すると,毎回先頭から計算するのではなく、前回書き込んだ位置からの差分として計算することも可能になるはずである。

2. Write trait を継承した struct を作り、それは write の戻り値を返すことで、何byte 書き込んだかを保持しておくことができる。

この方法では、 write の戻り値として、何byte書き込んだかというのを返してくれるので、それをカウンタに保持しておくことで、どのぐらい読んだかを知ることができる。この方法を実装したのが以下のライブラリである。

https://docs.rs/crate/count-write/0.1.0/source/src/lib.rs

ただしこの方法の欠点として、write_allwrite_fmt のような,戻り値でデータを書き込んだ長さを返さないようなデータ構造においては、その長さを取得できないという問題がある。

これ以外に、有用な方法はあるだろうか。

参考

https://stackoverflow.com/questions/34878970/how-to-get-current-cursor-position-in-file