I'm trying to implement a "polymorphic" Input
enum which hides whether we're reading from a file or from a stdin. More concretely, I'm trying build an enum that will have a lines
method that will in turn "delegate" that call to either a File
wrapped into a BufReader
or to a StdInLock
(both of which have the lines()
method).
Here's the enum:
enum Input<'a> {
Console(std::io::StdinLock<'a>),
File(std::io::BufReader<std::fs::File>)
}
I have three methods:
from_arg
for deciding whether we're reading from a file or from a stdin by checking whether an argument (filename) was provided,file
for wrapping a file with aBufReader
,console
for locking the stdin.
The implementation:
impl<'a> Input<'a> {
fn console() -> Input<'a> {
Input::Console(io::stdin().lock())
}
fn file(path: String) -> io::Result<Input<'a>> {
match File::open(path) {
Ok(file) => Ok(Input::File(std::io::BufReader::new(file))),
Err(_) => panic!("kita"),
}
}
fn from_arg(arg: Option<String>) -> io::Result<Input<'a>> {
Ok(match arg {
None => Input::console(),
Some(path) => try!(Input::file(path)),
})
}
}
As far as I understand, I have to implement both BufRead
and Read
traits for this to work. This is my attempt:
impl<'a> io::Read for Input<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Input::Console(ref mut c) => c.read(buf),
Input::File(ref mut f) => f.read(buf),
}
}
}
impl<'a> io::BufRead for Input<'a> {
fn lines(self) -> Lines<Self> {
match self {
Input::Console(ref c) => c.lines(),
Input::File(ref f) => f.lines(),
}
}
fn consume(&mut self, amt: usize) {
match *self {
Input::Console(ref mut c) => c.consume(amt),
Input::File(ref mut f) => f.consume(amt),
}
}
fn fill_buf(&mut self) -> io::Result<&[u8]> {
match *self {
Input::Console(ref mut c) => c.fill_buf(),
Input::File(ref mut f) => f.fill_buf(),
}
}
}
Finally, the invocation:
fn load_input<'a>() -> io::Result<Input<'a>> {
Ok(try!(Input::from_arg(env::args().skip(1).next())))
}
fn main() {
let mut input = match load_input() {
Ok(input) => input,
Err(error) => panic!("Failed: {}", error),
};
for line in input.lines() { /* do stuff */ }
}
Complete example in the playground
The compiler tells me that I'm pattern matching wrongly and that I have mismatched types
:
error[E0308]: match arms have incompatible types
--> src/main.rs:41:9
|
41 | / match self {
42 | | Input::Console(ref c) => c.lines(),
| | --------- match arm with an incompatible type
43 | | Input::File(ref f) => f.lines(),
44 | | }
| |_________^ expected enum `Input`, found struct `std::io::StdinLock`
|
= note: expected type `std::io::Lines<Input<'a>>`
found type `std::io::Lines<std::io::StdinLock<'_>>`
I tried to satisfy it with:
match self {
Input::Console(std::io::StdinLock(ref c)) => c.lines(),
Input::File(std::io::BufReader(ref f)) => f.lines(),
}
... but that doesn't work either.
I'm really out of my depth here, it seems.
This is the simplest solution but will borrow and lock
Stdin
.Due to default trait methods,
Read
andBufRead
are fully implemented forInput
. So you can calllines
onInput
.The answer by @A.B. is correct, but it tries to conform to OP's original program structure. I want to have a more readable alternative for newcomers who stumble upon this question (just like I did).
See the discussion in reddit from which I borrowed the code.