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
Open

add MultiReader and TeeReader #62

wants to merge 18 commits into from

Conversation

yuk1ty
Copy link
Owner

@yuk1ty yuk1ty commented May 4, 2021

対象の Issue

#15

動作確認結果

Go

Rust

MultiReader 側

❯ cargo run -p chapter3 --bin 3_7_multi
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/3_7_multi`
---- HEADER ----
Example of MultiReader
---- FOOTER ----

TeeReader 側

❯ cargo run -p chapter3 --bin 3_7_tee
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/3_7_tee`
Example of TeeReader

@yuk1ty yuk1ty marked this pull request as ready for review May 5, 2021 14:39
@yuk1ty yuk1ty requested a review from laysakura May 5, 2021 14:39
@yuk1ty yuk1ty added the レビュー待ち レビューする必要があります。 label May 5, 2021
@@ -0,0 +1,50 @@
use std::io::{Read, Write};

struct TeeReader<R, W>
Copy link
Collaborator

Choose a reason for hiding this comment

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

複数の問題で使うかはわかりませんが、TeeReaderも単機能で汎用性が高いので lib に移しませんか?🙂

/// Ok(())
/// }
/// ```
pub struct MultiReader<R> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

<R: Read> に制約して良いと思います。
(Read以外はコンストラクタがないので実際は作れませんが)

}
None => return Ok(0),
}
self.pos += 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

ここは「n個目のReadから読み切ったらn+1個目のReadに乗り換える」という動作をしていると思いますが、
Vec<R> の代わりに VecDeque<R> を使えば self.readers.pop_front() で先頭のReadを捨てることができ、よりわかりやすく記述できると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

あー、VecDeque 使ってみるのはありかもしれませんね。VecDeque なら for r in &mut self.readers みたいな形でイテレータ回すようにできるかも…?ちょっと検討してみます。

lib/src/io/multi_reader.rs Show resolved Hide resolved
Copy link
Contributor

@higumachan higumachan left a comment

Choose a reason for hiding this comment

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

GoのInterfaceは基本的にBox<dyn Trait>なのかもしれないですね。

/// }
/// ```
pub struct MultiReader<R: Read> {
readers: Vec<R>,
Copy link
Contributor

Choose a reason for hiding this comment

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

これだと同じ型のreadersしか持てないけど大丈夫でしょうか?
GoのMultiReaderがどうなってるのかはあまりわかってないですが。

https://golang.org/src/io/multi.go?s=1269:1311#L13

この定義的にはReaderなら何でも受け取れそう。

Copy link
Owner Author

Choose a reason for hiding this comment

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

Box<dyn Read> のほうが正しそうですね…!

Copy link
Contributor

@higumachan higumachan left a comment

Choose a reason for hiding this comment

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

MultiReaderのreaderの切れ目でのreadの挙動の確認

Comment on lines +43 to +52
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();
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を自分で管理していないので楽。

デメリット

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

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

@yuk1ty yuk1ty marked this pull request as draft May 9, 2021 01:48
@laysakura
Copy link
Collaborator

@yuk1ty Draftですが レビュー待ち ラベルが付いていることに気づきました。もしかしてreview readyですか? 👀

@yuk1ty
Copy link
Owner Author

yuk1ty commented May 17, 2021

コメントを付与したら review ready になるので、コメントを付与しちゃいますね

@yuk1ty yuk1ty removed the レビュー待ち レビューする必要があります。 label May 17, 2021
@yuk1ty yuk1ty marked this pull request as ready for review May 20, 2021 17:41
Copy link
Collaborator

@laysakura laysakura left a comment

Choose a reason for hiding this comment

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

一点確信のないコメントをしました。
そこ以外は別種の型のReadも取れるようになってて 🙆 です。

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() の結果になるので、このように別途フィールド(ステート)として持つ必要はないと思いますが、いかがでしょう?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants