LoginSignup
21
11

More than 3 years have passed since last update.

[Rust] Read と BufRead の違い

Posted at

ふたつのトレイト std::io::Read, std::io::BufRead の使い分けに毎回立ち止まるので, 覚えるために両者の役割の違いをまとめました. 具体的にユースケースが定まっているなら「Rustでファイルの入出力」という記事の方が便利だと思いますが, このふたつのトレイトの区別が曖昧な誰か (つまりこの記事を書く前の私と同じ状態の人) の役に立つと思うので公開します.

役割の違い

std::io::Read

Read が提供する read メソッドはシステムコールを呼ぶことが想定されていて, かなり低水準 (低レイヤー) なトレイトです. ですから提供するメソッドは read_to_endread_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::FileRead ですが BufRead ではありません.

使い分け

  • 最後まで読み込みたい, あるいは必要なデータがファイル冒頭から X byte と決まっているというケース. この場合は必要な分だけ読み込むシステムコールを 1 回発行すれば十分ですから, Read を使えばよく, BufRead を持ち出す必要はありません.
  • ファイルの中身を 1 行ずつ読みたい, あるいは区切り文字を指定して順に読みたい, といったケース. Read では必要な分を動的に判断して読み込むという器用なことはできないため, BufRead の範疇です.

こう考えれば .lines() したいときには Read ではなく BufReaduse しておく必要があることがすんなり理解できます.

BufReader について

ファイルの中身を X byte ずつ順番に読み込みたい, という状況では, 毎回システムコールするのは動作が遅いため, 一気にある程度読み込んで保持してくれる std::io::BufReader の出番です (関連記事). これは BufReaderBufRead であることに本質的に基づいていますが, X byte 読む (read する) 際に必要なのはあくまで Read です.

なお &[u8] それ自体 Read かつ BufRead であるため, 小さなファイルならファイル全体を Vec<u8> として読み込んで BufReader の代わりとして扱う, ということも可能です (参考).

参考文献

21
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
11