Rustでバイト列を扱う時のtips
このエントリはRust その2 Advent Calendar 2016 - Qiita2日目の記事です。
Rustはシステムプログラミング言語なのでバイト列をあれこれしたいことがあると思います。その時にイテレータでバイト列を舐める以外にも色々方法があるなと気付いたので。
Read
と Write
私には割と衝撃だったのですが&[u8]
や&mut [u8]
、Vec<u8>
は直接Read
やWrite
のインスタンスになってます。
例えばRead
ならこういう風に使えます。
let mut bytes: &[u8] = &[1, 2, 3, 4, 5, 6];
let mut buf = [0;3];
bytes.read_exact(&mut buf).unwrap();
println!("read: {:?}, rest: {:?}", buf, bytes);
read: [1, 2, 3], rest: [4, 5, 6]
あるいは .bytes()
でbyteのイテレータを取り出してもいいですし、如何様にも扱えます。
少し注意が必要なのはVec
は Read
を実装してないので一旦スライスに変換してあげる必要がありますが、やり方を工夫する必要があります。
以下は少しびっくりする例。
let bytes: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
let mut buf = [0;3];
(&bytes[..]).read_exact(&mut buf).unwrap();
println!("read: {:?}, rest: {:?}", buf, bytes);
read: [1, 2, 3], rest: [1, 2, 3, 4, 5, 6]
一旦スライスを取り出してその場で捨てているのでbytes
は消費されません。
そうやりたいなら
let bytes: Vec<u8> = vec![1, 2, 3, 4, 5, 6];
let mut bytes = &bytes[..];
let mut buf = [0;3];
bytes.read_exact(&mut buf).unwrap();
println!("read: {:?}, rest: {:?}", buf, bytes);
のように一旦スライスを束縛してから使います。
Writeの方も似ていて、そのままwrite
出来ますし、消費されます。ただ、書き込み領域が空になってもそのままスルーされるので注意です。
let mut bytes: &mut [u8] = &mut [0; 6];
let data = &[1, 2, 3];
println!("data: {:?}, buf: {:?}", data, bytes);
bytes.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, bytes);
bytes.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, bytes);
bytes.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, bytes);
data: [1, 2, 3], buf: [0, 0, 0, 0, 0, 0]
data: [1, 2, 3], buf: [0, 0, 0]
data: [1, 2, 3], buf: []
data: [1, 2, 3], buf: []
最後、バッファが空になった状態で書き込んでも無言で書き込みが終了しています。そして書き込んだデータへのアクセスは出来てないですね。
こうすると出来ます。
let mut bytes:[u8; 6] = [0; 6];
let data = &[1, 2, 3];
{
let mut buf: &mut [u8] = &mut bytes;
println!("data: {:?}, buf: {:?}", data, buf);
buf.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, buf);
buf.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, buf);
}
println!("bytes: {:?}", bytes);
data: [1, 2, 3], buf: [0, 0, 0, 0, 0, 0]
data: [1, 2, 3], buf: [0, 0, 0]
data: [1, 2, 3], buf: []
bytes: [1, 2, 3, 1, 2, 3]
まあまあ面倒ですね。というかそもそも固定長のバッファに書き込みたいという需要が少ない。
でもこれはVec
を使えば解決します。可変長ですしWrite
も実装しているので便利です。
let mut bytes: Vec<u8> = Vec::new();
let data = &[1, 2, 3];
println!("data: {:?}, buf: {:?}", data, bytes);
bytes.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, bytes);
bytes.write(data).unwrap();
println!("data: {:?}, buf: {:?}", data, bytes);
println!("bytes: {:?}", bytes);
data: [1, 2, 3], buf: []
data: [1, 2, 3], buf: [1, 2, 3]
data: [1, 2, 3], buf: [1, 2, 3, 1, 2, 3]
bytes: [1, 2, 3, 1, 2, 3]
Cursor
さて、上で見た通り、なんとなく生のバイト列だと扱いづらそうな場面がありそうですよね。
そこで std::io::Cursor
を使うと便利です。
Cursor
はコンストラクタで引数の所有権を奪うタイプの、ラッパーオブジェクト的構造体です。
readだとこんな感じです。
let bytes: &[u8] = &[1,2,3,4,5,6];
let data: &mut [u8] = &mut [0;3];
let mut cur = Cursor::new(bytes);
println!("data: {:?}, position {}", data, cur.position());
cur.read_exact(data).unwrap();
println!("data: {:?}, position {}", data, cur.position());
cur.read_exact(data).unwrap();
println!("data: {:?}, position {}", data, cur.position());
println!("bytes: {:?}", cur.into_inner());
data: [0, 0, 0], position 0
data: [1, 2, 3], position 3
data: [4, 5, 6], position 6
bytes: [1, 2, 3, 4, 5, 6]
ポジションが取れるのと元のオブジェクトが無事なのが違いますね。
Writeも同様です。
let bytes: &mut [u8] = &mut [0;6];
let data: &[u8] = &[1, 2, 3];
let mut cur = Cursor::new(bytes);
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
cur.write(data).unwrap();
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
cur.write(data).unwrap();
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
println!("bytes: {:?}", cur.into_inner());
data: [0, 0, 0, 0, 0, 0], position 0
data: [1, 2, 3, 0, 0, 0], position 3
data: [1, 2, 3, 1, 2, 3], position 6
bytes: [1, 2, 3, 1, 2, 3]
さて、このCursor
の面白いのは&[u8]
でなくAsRef<[u8]>
でRead
を実装していますしstd::io::Seek
も実装しているのでこういうことが出来ます。
let bytes: Vec<u8> = Vec::new();
let data: &[u8] = &[1, 2, 3];
let buf: &mut [u8] = &mut [0; 3];
let mut cur = Cursor::new(bytes);
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
cur.write(data).unwrap();
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
cur.write(data).unwrap();
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
cur.seek(SeekFrom::Start(0)).unwrap();
println!("bytes: {:?}, position {}", cur.get_ref(), cur.position());
cur.read(buf).unwrap();
println!("bytes: {:?}, position {}, buf: {:?}", cur.get_ref(), cur.position(), buf);
bytes: [], position 0
bytes: [1, 2, 3], position 3
bytes: [1, 2, 3, 1, 2, 3], position 6
bytes: [1, 2, 3, 1, 2, 3], position 0 // <- 0にシークした
bytes: [1, 2, 3, 1, 2, 3], position 3, buf: [1, 2, 3] // <- 0からリード出来てる
生のVec
では出来なかったread and writeが実現出来ています。そして好きにカーソルをシーク出来ます。
ここまでくるとほとんどファイルと変わらなく扱えますね。
おりに
ちょっとしたTipsですが道具箱にこういうのを増やしておくと便利ですよね!!