Search code examples
rustiostdintrait-objectsmonomorphism

How to handle BufReader<File> and BufReader<Stdin> without dynamic dispatch?


I'm trying to create an object that can contain BufReader<File> and BufReader<Stdin> simultaneously. I'd like to avoid the more commonly used "trait object" (i.e. Box<dyn BufRead>), but instead use something I learned from https://gist.github.com/rust-play/dda485b47144b8228b3cc412d2a27e06.

Below is the code.

enum BufInput {
    File(io::BufReader<fs::File>),
    Stdin(io::BufReader<io::Stdin>),
}

impl io::Read for BufInput {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match self {
            BufInput::File(f) => f.read(buf),
            BufInput::Stdin(s) => s.read(buf),
        }
    }
}

impl io::BufRead for BufInput {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        match self {
            BufInput::File(f) => f.fill_buf(),
            BufInput::Stdin(s) => s.fill_buf(),
        }
    }

    fn consume(&mut self, amt: usize) {
        match self {
            BufInput::File(f) => f.consume(amt),
            BufInput::Stdin(s) => s.consume(amt),
        }
    }

    fn lines(self) -> io::Lines<Self> {
        match self {
            BufInput::File(f) => f.lines(), 
            //                   ^^^^^^^^^  expected `Lines<BufInput>`, found `Lines<BufReader<File>>`
            BufInput::Stdin(s) => s.lines(),
        }
    }
}

The only thing that doesn't work is fn lines() in trait BufRead. Any idea? Thanks.


Solution

  • You don't need to implement lines(), it has a default implementation.


    Implementing a custom lines() could in theory be optimal if your variants had custom lines() implementations of their own. However, all of the standard library implementations of BufRead just use the default implementation. And even outside of theory, in practice a custom lines() implementation couldn't do much anyway since it is required to return Lines<Self>, which does all the work by calling the other methods.

    You only need to implement fill_buf() and consume() to have a working BufRead implementation, but if you want to go for optimal in general you could implement read_until() and read_line() that forward directly to their variants since those are more likely to have optimized (non-default) implementations. However, in your case where all variants are BufReader, this won't have any practical effect since BufReader just uses the default implementation for those methods as well.

    So just use what you have without implementing lines().