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 a BufReader
,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.
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).
use std::env;
use std::fs;
use std::io::{self, BufReader, BufRead};
fn main() {
let input = env::args().nth(1);
let reader: Box<dyn BufRead> = match input {
None => Box::new(BufReader::new(io::stdin())),
Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap()))
};
for line in reader.lines() {
println!("{:?}", line);
}
}
See the discussion in reddit from which I borrowed the code.
Note the dyn
keyword before boxed BufRead. This pattern is called a trait object.
This is the simplest solution but will borrow and lock Stdin
.
use std::fs::File;
use std::io::{self, BufRead, Read};
struct Input<'a> {
source: Box<BufRead + 'a>,
}
impl<'a> Input<'a> {
fn console(stdin: &'a io::Stdin) -> Input<'a> {
Input {
source: Box::new(stdin.lock()),
}
}
fn file(path: &str) -> io::Result<Input<'a>> {
File::open(path).map(|file| Input {
source: Box::new(io::BufReader::new(file)),
})
}
}
impl<'a> Read for Input<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.source.read(buf)
}
}
impl<'a> BufRead for Input<'a> {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
self.source.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.source.consume(amt);
}
}
Due to default trait methods, Read
and BufRead
are fully implemented for Input
. So you can call lines
on Input
.
let input = Input::file("foo.txt").unwrap();
for line in input.lines() {
println!("input line: {:?}", line);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With