ふたつのトレイト std::io::Read
, std::io::BufRead
の使い分けに毎回立ち止まるので, 覚えるために両者の役割の違いをまとめました. 具体的にユースケースが定まっているなら「Rustでファイルの入出力」という記事の方が便利だと思いますが, このふたつのトレイトの区別が曖昧な誰か (つまりこの記事を書く前の私と同じ状態の人) の役に立つと思うので公開します.
役割の違い
std::io::Read
Read
が提供する read
メソッドはシステムコールを呼ぶことが想定されていて, かなり低水準 (低レイヤー) なトレイトです. ですから提供するメソッドは read_to_end
や read_to_string
のようにまるっと読み込む系だけです. 特定のバイトが現れたら読み込みをストップする, というような高水準な操作はこのレイヤーで扱う範疇には含まれません.
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> Result<usize> { ... }
unsafe fn initializer(&self) -> Initializer { ... }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{ ... }
fn bytes(self) -> Bytes<Self>
where
Self: Sized,
{ ... }
fn chain<R: Read>(self, next: R) -> Chain<Self, R>
where
Self: Sized,
{ ... }
fn take(self, limit: u64) -> Take<Self>
where
Self: Sized,
{ ... }
}
std::io::BufRead
一方の BufRead
はより高水準なトレイトで, 手元にバッファーを保持しておき, 必要になったら内部で自動的にシステムコールする, という機能を提供するインスタンスを表します. それに見合って提供するメソッドも比較的高機能で, 特定のバイトまでを返す read_until
, 1 行だけを返す read_line
, 特定のバイトまでのバイト列を返すイテレータ split
, 1 行ずつ返すイテレータ lines
となっています. Read
と違い, 手元にデータを保持しているために「特定のバイトまでを返す」といった処理ができるのです.
pub trait BufRead: Read {
fn fill_buf(&mut self) -> Result<&[u8]>;
fn consume(&mut self, amt: usize);
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_line(&mut self, buf: &mut String) -> Result<usize> { ... }
fn split(self, byte: u8) -> Split<Self>
where
Self: Sized,
{ ... }
fn lines(self) -> Lines<Self>
where
Self: Sized,
{ ... }
}
なお std::fs::File
は Read
ですが BufRead
ではありません.
使い分け
- 最後まで読み込みたい, あるいは必要なデータがファイル冒頭から X byte と決まっているというケース. この場合は必要な分だけ読み込むシステムコールを 1 回発行すれば十分ですから,
Read
を使えばよく,BufRead
を持ち出す必要はありません. - ファイルの中身を 1 行ずつ読みたい, あるいは区切り文字を指定して順に読みたい, といったケース.
Read
では必要な分を動的に判断して読み込むという器用なことはできないため,BufRead
の範疇です.
こう考えれば .lines()
したいときには Read
ではなく BufRead
を use
しておく必要があることがすんなり理解できます.
BufReader
について
ファイルの中身を X byte ずつ順番に読み込みたい, という状況では, 毎回システムコールするのは動作が遅いため, 一気にある程度読み込んで保持してくれる std::io::BufReader
の出番です (関連記事). これは BufReader
が BufRead
であることに本質的に基づいていますが, X byte 読む (read
する) 際に必要なのはあくまで Read
です.
なお &[u8]
それ自体 Read
かつ BufRead
であるため, 小さなファイルならファイル全体を Vec<u8>
として読み込んで BufReader
の代わりとして扱う, ということも可能です (参考).