Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add MultiReader and TeeReader #62

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions chapter3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,9 @@ path = "src/3_6_3/main.rs"
[[bin]]
name = "3_6_2"
path = "src/3_6_2/main.rs"
[[bin]]
name = "3_7_multi"
path = "src/3_7/multi_reader.rs"
[[bin]]
name = "3_7_tee"
path = "src/3_7/tee_reader.rs"
14 changes: 14 additions & 0 deletions chapter3/src/3_7/multi_reader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::io::{copy, stdout};

use lib::io::MultiReader;

fn main() -> std::io::Result<()> {
let header = "---- HEADER ----\n".as_bytes();
let content = "Example of MultiReader\n".as_bytes();
let footer = "---- FOOTER ----\n".as_bytes();
let mut multi_reader =
MultiReader::new(vec![Box::new(header), Box::new(content), Box::new(footer)]);
copy(&mut multi_reader, &mut stdout())?;

Ok(())
}
20 changes: 20 additions & 0 deletions chapter3/src/3_7/tee_reader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use lib::io::TeeReader;
use std::io::Read;

fn main() -> std::io::Result<()> {
// TeeReader を使用した場合の例。
let mut buf = Vec::new();
let reader = "Example of TeeReader".as_bytes();
let mut tee_reader = TeeReader::new(reader, &mut buf);
// データを読み捨てる。
let _ = tee_reader.read_to_end(&mut Vec::new())?;

println!(
"{}",
// けどバッファには残っている。
String::from_utf8(buf)
.expect("UTF-8 形式でない文字列の可能性があります。UTF-8 にしてください。")
);

Ok(())
}
4 changes: 4 additions & 0 deletions lib/src/io.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
mod limited_reader;
mod multi_reader;
mod multi_writer;
mod section_reader;
mod tee_reader;

pub use limited_reader::LimitedReader;
pub use multi_reader::MultiReader;
pub use multi_writer::MultiWriter;
pub use section_reader::SectionReader;
pub use tee_reader::TeeReader;
58 changes: 58 additions & 0 deletions lib/src/io/multi_reader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::{collections::VecDeque, io::Read};

/// Takes multiple `std::io::Read` at once.
/// This is inspired by `io.MultiReader` in Go.
///
/// # Example
///
/// ```
/// use std::io::{copy, stdout};
/// use lib::io::MultiReader;
///
/// fn main() -> std::io::Result<()> {
/// let header = "---- HEADER ----\n".as_bytes();
/// let content = "Example of MultiReader\n".as_bytes();
/// let footer = "---- FOOTER ----\n".as_bytes();
/// let mut multi_reader =
/// MultiReader::new(vec![Box::new(header), Box::new(content), Box::new(footer)]);
/// copy(&mut multi_reader, &mut stdout())?;
/// Ok(())
/// }
/// ```
pub struct MultiReader {
readers: VecDeque<Box<dyn Read>>,
/// Points to current element while reading.
current: Option<Box<dyn Read>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

直感的にはこれは常に readers.front() の結果になるので、このように別途フィールド(ステート)として持つ必要はないと思いますが、いかがでしょう?

}

impl MultiReader {
/// Constructs `MultiReader`. `current` is set to the first element that is popped out from `VecDeque`.
pub fn new(readers: Vec<Box<dyn Read>>) -> Self {
let mut deque: VecDeque<Box<dyn Read>> = readers.into();
let current = deque.pop_front();
Self {
readers: deque,
current,
}
}
}

impl Read for MultiReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// Doesn't move to the next one until the first one is completely read.
// If the processing ends in the middle of reading the first one,
// it returns the control at that point.
loop {
match self.current {
Some(ref mut r) => {
let n = r.read(buf)?;
if n > 0 {
return Ok(n);
}
}
None => return Ok(0),
}
self.current = self.readers.pop_front();
Comment on lines +46 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Goの実装に依るのですが

ここの実装はreadersの切れ目で使う側の実装によっては強制的に読み込みが終了してしまう気がしています。

一般的にReadを使う場合でRead読み切ったかどうかを判断する場合したのようなコードを書きそうな気がしています。

let mut buf: [u8; 128];
let mut some_reader;

while some_reader.read(&mut buf) >= buf.len() {
   ...
}

その場合にReadの切れ目でbuf.sizeより小さな数字が返ってくると中途半端な状態で終了してしまいそうかなと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go の実装はこちらですね(ただ、もともと参考にはしていなくて、だいぶ実装の形が違っています)
https://golang.org/src/io/multi.go?s=2446:2488

Go の実装に寄せるのはありかもです。

ここの実装はreadersの切れ目で使う側の実装によっては強制的に読み込みが終了してしまう気がしています。

申し訳ないですが、ちょっと具体的な例がイメージできておらず…!なにかコーナーケースをパッと思いついたりしますでしょうか?修正するにしても、理解を深めてから修正したいなと思っており。

Copy link
Contributor

@higumachan higumachan May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コーナーケースの具体例とその上での僕の想定は以下になります。(github上で書いたので雰囲気Rustですが)

#[test]
fn multi_reader_end_of_first_reader() {
    let header = "---- HEADER ----\n".as_bytes();
    let content = "Example of MultiReader\n".as_bytes();
    let mut multi_reader = MultiReader::new(vec![Box::new(header), Box::new(content)]);

    let mut buf: Vec<u8> = vec![0; 256];

    let size = multi_reader.read(&mut buf).unwrap();

    assert_eq!(size, header.len() + content.len());  // ここが現状の実装だとsize == header.len()になってそう?
}

Copy link
Contributor

@higumachan higumachan May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ちなみに、Goの実装を読んでみたら僕の読解が正しければ errとしてEOFは返さないけどbuf.len未満のsizeを返す(size == header.len()) になる感じでした。

Rustは通常のReadではEOFをErrとして扱わないので厄介ですね。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上記コードを実行してみた結果、たしかに落ちています。考えてみますね。

running 1 test
test io::multi_reader::multi_reader_end_of_first_reader ... FAILED

failures:

---- io::multi_reader::multi_reader_end_of_first_reader stdout ----
thread 'io::multi_reader::multi_reader_end_of_first_reader' panicked at 'assertion failed: `(left == right)`
  left: `17`,
 right: `40`', lib/src/io/multi_reader.rs:67:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    io::multi_reader::multi_reader_end_of_first_reader

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Copy link
Contributor

@higumachan higumachan May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuk1ty
めちゃくちゃ気になっちゃっていろいろ調べてみました。
結論現状の実装のままで問題なさそうです。

Rustの標準ライブラリのコード(read_to_endとかChainとか)を読んでみたんですがRustのReadでは返り値0以外は特別扱いしてはいけないみたいです。

ですので、僕の書いた

let mut buf: [u8; 128];
let mut some_reader;

while some_reader.read(&mut buf) >= buf.len() {
   ...
}

上のようなコードは良くないみたいです。(コンパイル時に検知できないのはRustらしくないなとは思いましたが…)

Copy link
Contributor

@higumachan higumachan May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あと、Readを改めて調べてみたら Read::chainっていうのを見つけたんですがこれを使うとMultiReaderを簡単に実装できないですかね?(学習用なら残しても良さそうですが)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これを使うとMultiReaderを簡単に実装できないですかね?(学習用なら残しても良さそうですが)

実装してみたところ、出来たけどあんまり簡単じゃなかったです。

Copy link
Owner Author

@yuk1ty yuk1ty May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。

Read::Chain は2つずつしかつなげられなくて、結構工夫がいりそうだったんですよね。car-cdr みたいな感じでリストを組んで、再帰的にたどる手でいけるかなとか妄想はしましたが、Vec をはじめとするベクタ系の方が実装が直感的と思って、そちらを採用していたという経緯があります😌

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

car-cdr みたいな感じでリストを組んで、再帰的にたどる手でいけるかなとか妄想はしましたが、Vec をはじめとするベクタ系の方が実装が直感的と思って、そちらを採用していたという経緯があります😌

おっしゃるとおりですね。
参考までに実装を書いておきます。

pub struct AnotherMultiReader {
    reader: Option<Chain<Box<dyn Read>, Box<dyn Read>>>,
}

impl Read for AnotherMultiReader {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.reader.as_mut().map(|x| x.read(buf)).unwrap_or(Ok(0))
    }
}

impl AnotherMultiReader {
    pub fn new(readers: Vec<Box<dyn Read>>) -> Self {
        Self::new_impl(readers.into())
    }

    fn new_impl(mut readers: VecDeque<Box<dyn Read>>) -> Self {
        let first = readers.pop_front();
        first
            .map(|r| {
                let remain: Box<dyn Read> = Box::new(AnotherMultiReader::new_impl(readers));
                Self {
                    reader: Some(r.chain(remain)),
                }
            })
            .unwrap_or(Self { reader: None })
    }
}

メリット(AnotherMultiReaderの)

  • コーナーケース系のエンバグは少なさそう(Chainが十分テストされてるはずなので)
  • stateを自分で管理していないので楽。

デメリット

  • 実装が非直感的
  • 教育的なコードではない(と感じる)

という感じなのでそのままで大丈夫だと思います。

}
}
}
32 changes: 32 additions & 0 deletions lib/src/io/tee_reader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::io::{Read, Write};

pub struct TeeReader<R, W>
where
R: Read,
W: Write,
{
reader: R,
writer: W,
}

impl<R, W> TeeReader<R, W>
where
R: Read,
W: Write,
{
pub fn new(reader: R, writer: W) -> Self {
Self { reader, writer }
}
}

impl<R, W> Read for TeeReader<R, W>
where
R: Read,
W: Write,
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.reader.read(buf)?;
self.writer.write_all(&buf[..n])?;
Ok(n)
}
}