Rust Generic Function Attempt for Async Reads - rust

My goal is to reduce the following read_stream_***() functions into a generic fuction that can be passed different streams.
use async_std::net::TcpStream;
use async_std::{ task };
use async_std::io::{ stdin, BufReader, Stdin };
use async_std:: { prelude::* };
use futures::{select, FutureExt, AsyncRead };
pub async fn read_stream_stdin(streem:Stdin) -> Result<(), std::io::Error>
{
let mut lines_from_stream = BufReader::new(streem).lines().fuse();
loop {
select! {
line = lines_from_stream.next().fuse() => match line {
Some(line) => {
println!("{:?}",line?);
}
None => break,
}
}
}
Ok(())
}
pub async fn read_stream_tcp(streem:TcpStream) -> Result<(), std::io::Error>
{
let mut lines_from_stream = BufReader::new(streem).lines().fuse();
loop {
select! {
line = lines_from_stream.next().fuse() => match line {
Some(line) => {
println!("{:?}",line?);
}
None => break,
}
}
}
Ok(())
}
pub async fn connect_tcp_server(host_port:&str) -> Result<(), std::io::Error>
{
let streem = TcpStream::connect(host_port).await;
let _result = task::block_on(read_stream_tcp(streem?));
Ok(())
}
fn main() -> Result<(), std::io::Error> {
task::spawn( connect_tcp_server("127.0.0.1:8081") );
task::block_on(read_stream_stdin(stdin()))
}
The Generic Attempt:
pub async fn read_stream<T>(streem:T) -> Result<(), std::io::Error>
{
let mut lines_from_stream = BufReader::new(streem).lines().fuse();
loop {
select! {
line = lines_from_stream.next().fuse() => match line {
Some(line) => {
println!("{:?}",line?);
}
None => break,
}
}
}
Ok(())
}
The Cargo.toml
[package]
name = "gen_func"
version = "0.1.0"
edition = "2021"
[dependencies]
async-std = "1.9.0"
futures = "0.3.21"
I attempted <T: async_std::io::Read> but fuse() and lines() are not implemented. and AsyncRead is not found in async_std::io . I found AsyncRead in futures crate but again fuse() and lines() were not implemented. I am not set on the read pattern. I am new to Rust and trying to build my source library to solve future programming tasks.

First, as pointed out by kmdreko, the logic of your function(s) can be greatly simplified (at least based on the information given):
pub async fn read_stream_tcp(stream: TcpStream) -> Result<(), std::io::Error> {
let mut lines = BufReader::new(stream).lines();
while let Some(line) = lines.next().await {
println!("{line:?}");
}
}
Ok(())
Then, to figure out how to make this generic, you can just let the compiler tell you what it needs:
pub async fn read_stream<T>(stream: T) -> Result<(), std::io::Error>
{
let mut lines = BufReader::new(stream).lines();
while let Some(line) = lines.next().await {
println!("{line:?}");
}
Ok(())
}
Notice the lack of where clauses or other constraints on T. The compiler will now complain:
error[E0277]: the trait bound `T: async_std::io::Read` is not satisfied --> src/main.rs:15:36 |
15 | let mut lines = BufReader::new(stream).lines();
| -------------- ^^^^^^ the trait `async_std::io::Read` is not implemented for `T`
| |
| required by a bound introduced by this call
|
note: required by a bound in `async_std::io::BufReader::<R>::new`
--> /home/lucas/.cargo/registry/src/github.com-1ecc6299db9ec823/async-std-1.12.0/src/io/buf_reader.rs:55:9
|
55 | impl<R: io::Read> BufReader<R> {
| ^^^^^^^^ required by this bound in `async_std::io::BufReader::<R>::new`
help: consider restricting type parameter `T`
|
13 | pub async fn read_stream<T: async_std::io::Read>(stream: T) -> Result<(), std::io::Error>
| +++++++++++++++++++++
Applying the compiler's suggestions (the above will result in a follow-up error) yields a full where clause of T: async_std::io::Read + std::marker::Unpin:
pub async fn read_stream<T>(stream: T) -> Result<(), std::io::Error>
where
T: Read + std::marker::Unpin,
{
let mut lines = BufReader::new(stream).lines();
while let Some(line) = lines.next().await {
println!("{line:?}");
}
Ok(())
}
async fn try_it() {
// These will now compile just fine
read_stream(async_std::io::stdin()).await.unwrap();
read_stream(TcpStream::connect("127.0.0.1:8080").await.unwrap()).await.unwrap();
}
I attempted <T: async_std::io::Read> but fuse() and lines() are not implemented
This suggests that you tried replacing BufReader::new(stream) at the same time. You can do that, but you need to tell the compiler that you need something that implements the lines() method. Either make the parameter a fixed type BufReader<T> or make the where clause T: async_std::io::BufRead + std::marker::Unpin for a generic type.

Related

lifetime may not live long enough when passing &mut inside async closure?

I want to write function transactional which handle logic related to transactions committing
fn get_client() -> Client {
return Client{};
}
struct Client {}
impl Client {
pub async fn commit(&mut self) -> Result<(), Error> {
return Ok(());
}
pub async fn find_and_update(&mut self) -> Vec<u64> {
return vec![];
}
}
pub async fn transactional<F, Fut, R>(action: F) -> Result<R, Error>
where
F: Fn(&mut Client) -> Fut,
Fut: Future<Output = R>
{
let mut client = get_client();
loop {
let action_result = action(&mut client).await;
if let Err(err) = client.commit().await {
continue;
}
return Ok(action_result);
}
}
pub async fn make_request() -> Vec<u64> {
return transactional(
async move |session| session.find_and_update().await
).await.unwrap();
}
#[tokio::main]
async fn main() -> Result<(), io::Error>{
let r = make_request().await;
return Ok(())
}
but i get following error
| async move |session| session.find_and_update().await
| ^^^^^^^^^^^^--------
| | | |
| | | return type of closure `impl futures::Future<Output = Vec<u64>>` contains a lifetime `'2`
| | has type `&'1 mut Client`
| returning this value requires that `'1` must outlive `'2`
is it possible to specify that &Client outlives Future and both lives less than loop iteration?
is it possible to fix this wihtout using pointers?
cargo --version
cargo 1.64.0-nightly (a5e08c470 2022-06-23)
At first I rewrote your code to not use unstable async closures. Then you see, that borrowing Client in an async block |session| async move {..} is only possible for 'static and you don't have it. So you need to give it an owned value. In my case I pass ownership to the async block and return it in the result. Not sure if this is a good design, but it works.
use std::future::Future;
use tokio; // 1.19.2
fn get_client() -> Client {
return Client{};
}
pub struct Client {}
impl Client {
pub async fn commit(&mut self) -> Result<(), Error> {
return Ok(());
}
pub async fn find_and_update(&mut self) -> Vec<u64> {
return vec![];
}
}
pub async fn transactional<F, Fut, R>(action: F) -> Result<R, Error>
where
F: Fn(Client) -> Fut,
Fut: Future<Output = (R, Client)>
{
let mut client = get_client();
loop {
let (action_result, c) = action(client).await;
client = c;
if let Err(err) = client.commit().await {
continue;
}
return Ok(action_result);
}
}
pub async fn make_request() -> Vec<u64> {
return transactional(|mut session| async move { // move `client` into async, not borrow
let r = session.find_and_update().await;
(r, session)
}).await.unwrap();
}
#[tokio::main]
async fn main() -> Result<(), std::io::Error>{
let r = make_request().await;
return Ok(())
}
#[derive(Debug)]
pub struct Error;

rust how to make a variable has different types in different conditions

fn main() -> std::io::Result<()> {
let path = std::env::args().nth(1).expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
let writer;
if output_path.is_none() {
writer = std::io::stdout();
} else {
writer = std::fs::File::open(output_path.unwrap()).unwrap();
}
minidecaf::run(&input, &mut writer)
}
writer should be stdout() when output_path command line argument is not provided and be File when output_path command line argument is provided.
This code does not work, because the type of writer cannot be determined in compile time.
I change it to the following. Since both File and Stdout implements std::io::Write, and what I need is a reference to std::io::Write. As the signature of function run is:
pub fn run(input: &str, output: &mut impl std::io::Write) -> std::io::Result<()>
fn main() -> std::io::Result<()> {
let path = std::env::args().nth(1).expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
let mut writer : &mut std::io::Write;
if output_path.is_none() {
writer = &mut std::io::stdout();
} else {
writer = &mut std::fs::File::open(output_path.unwrap()).unwrap();
}
minidecaf::run(&input, &mut writer)
}
But it does not work, too. It has lifetime problems.
Then I change it to:
fn main() -> std::io::Result<()> {
let path = std::env::args().nth(1).expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
minidecaf::run(&input, &mut (if output_path.is_none() {std::io::stdout()} else {std::fs::File::open(output_path.unwrap()).unwrap()}) )
}
It has the same problem with the first piece of code.
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:11:83
|
11 | minidecaf::run(&input, &mut (if output_path.is_none() {std::io::stdout()} else {std::fs::File::open(output_path.unwrap()).unwrap()}) )
| ----------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Stdout`, found struct `File`
| |
| expected because of this
So how can I make writer be stdout() when output_path command line argument is not provided and be File when output_path command line argument is provided?
As you noted, you need writer to be some kind of reference to std::io::Write, but you also need someone to own the corresponding value. The obvious solution here is to use a Box:
fn run(input: &str, output: &mut impl std::io::Write) -> std::io::Result<()> {
unimplemented!();
}
fn main() -> std::io::Result<()> {
let path = std::env::args()
.nth(1)
.expect("usage: minidecaf <input path>");
let input = std::fs::read_to_string(path)?;
let output_path = std::env::args().nth(2);
let mut writer: Box<dyn std::io::Write>;
if output_path.is_none() {
writer = Box::new(std::io::stdout());
} else {
writer = Box::new(std::fs::File::open(output_path.unwrap()).unwrap());
}
run(&input, &mut writer)
}
Playground
I think you may want to read more about how to use trait.
Here is a sample code may do what you want:
#[derive(Default)]
struct A {
a: usize,
}
#[derive(Default)]
struct B {
b: usize,
}
trait C {
fn test(&self);
}
impl C for A {
fn test(&self) {
println!("A");
}
}
impl C for B {
fn test(&self) {
println!("B");
}
}
fn main() {
let t: Box<dyn C> = if true {
Box::new(A::default())
} else {
Box::new(B::default())
};
t.test();
}
You can try it Here
To avoid the overhead of dynamic dispatch, it is possible to use the Either type from the either crate to wrap the value, which automatically implements all (most?) traits from the standard library if possible:
use {
either::Either,
std::{fs::File, io},
};
let mut writer = match output_path {
Some(path) => Either::Left(File::open(path)?),
None => Either::Right(io::stdout()),
};
(playground)
Here, using a match expression is conceptually clearer in my opinion.
Without using external crates, it is also possible to mimic the behavior of an Either enum using variables:
use std::{env, fs::File, io};
let (mut stdout, mut file);
let writer: &mut dyn io::Write = match output_path {
Some(path) => {
file = File::open(path)?;
&mut file
}
None => {
stdout = io::stdout();
&mut stdout
}
};
(playground)

How to run stream to completion in Rust using combinators (other than for_each) and without a while loop?

I have a stream which uses combinators and I need to run it to completion. I can use a while loop or the for_each combinator. They will both work, but I think there must be a nicer way.
Sink looks like what I'm looking for, especially sink::drain(), but I havn't been able to understand how to use it.
Using while loop
use futures::{StreamExt, TryStreamExt}; // 0.3.6
use tokio; // 0.3.0
#[tokio::main]
async fn main() {
let mut stream = Box::pin(
futures::stream::iter(0..20)
.map(foo)
.map_ok(|x| x * 10)
.and_then(bar)
.filter(|x| futures::future::ready(x.is_ok())),
);
while let Some(_) = stream.next().await {
// Nothing to do here. I just need to run stream.
}
}
fn foo(x: i32) -> Result<i32, String> {
if x != 10 {
Ok(x)
} else {
Err("eeer".to_string())
}
}
async fn bar(x: i32) -> Result<(), String> {
async {
if x == 13 {
Err("errr".to_string())
} else {
Ok(())
}
}
.await
}
Using for_each:
use futures::{StreamExt, TryStreamExt}; // 0.3.6
use tokio; // 0.3.0
#[tokio::main]
async fn main() {
futures::stream::iter(0..20)
.map(foo)
.map_ok(|x| x * 10)
.and_then(bar)
.filter(|x| futures::future::ready(x.is_ok()))
.for_each(|_| futures::future::ready(())) // Nothing to do here, just to run stream
.await;
}
fn foo(x: i32) -> Result<i32, String> {
if x != 10 {
Ok(x)
} else {
Err("eeer".to_string())
}
}
async fn bar(x: i32) -> Result<(), String> {
async {
if x == 13 {
Err("errr".to_string())
} else {
Ok(())
}
}
.await
}
I would like to have something like the following. It's not necessary to use the drain combinator exactly, just some combinator to run the stream:
use futures::{StreamExt, TryStreamExt}; // 0.3.6
use tokio; // 0.3.0
#[tokio::main]
async fn main() {
futures::stream::iter(0..20)
.map(foo)
.map_ok(|x| x * 10)
.and_then(bar)
.filter(|x| futures::future::ready(x.is_ok()))
.forward(futures::sink::drain())
.await;
}
fn foo(x: i32) -> Result<i32, String> {
if x != 10 {
Ok(x)
} else {
Err("eeer".to_string())
}
}
async fn bar(x: i32) -> Result<(), String> {
async {
if x == 13 {
Err("errr".to_string())
} else {
Ok(())
}
}
.await
}
This doesn't work, probably because drain puts some bounds on the Error type:
error[E0271]: type mismatch resolving `<futures::sink::Drain<()> as futures::Sink<()>>::Error == std::string::String`
--> src/main.rs:11:10
|
11 | .forward(futures::sink::drain())
| ^^^^^^^ expected enum `std::convert::Infallible`, found struct `std::string::String`
error[E0271]: type mismatch resolving `<futures::stream::Filter<futures::stream::AndThen<futures::stream::MapOk<futures::stream::Map<futures::stream::Iter<std::ops::Range<i32>>, fn(i32) -> std::result::Result<i32, std::string::String> {foo}>, [closure#src/main.rs:8:17: 8:27]>, impl futures::Future, fn(i32) -> impl futures::Future {bar}>, futures::future::Ready<bool>, [closure#src/main.rs:10:17: 10:54]> as futures::Stream>::Item == std::result::Result<(), std::convert::Infallible>`
--> src/main.rs:6:5
|
6 | / futures::stream::iter(0..20)
7 | | .map(foo)
8 | | .map_ok(|x| x * 10)
9 | | .and_then(bar)
10 | | .filter(|x| futures::future::ready(x.is_ok()))
11 | | .forward(futures::sink::drain())
12 | | .await;
| |______________^ expected struct `std::string::String`, found enum `std::convert::Infallible`
|
= note: expected enum `std::result::Result<_, std::string::String>`
found enum `std::result::Result<_, std::convert::Infallible>`
= note: required because of the requirements on the impl of `futures::Future` for `futures_util::stream::stream::forward::Forward<futures::stream::Filter<futures::stream::AndThen<futures::stream::MapOk<futures::stream::Map<futures::stream::Iter<std::ops::Range<i32>>, fn(i32) -> std::result::Result<i32, std::string::String> {foo}>, [closure#src/main.rs:8:17: 8:27]>, impl futures::Future, fn(i32) -> impl futures::Future {bar}>, futures::future::Ready<bool>, [closure#src/main.rs:10:17: 10:54]>, futures::sink::Drain<()>, ()>`
Sink trait is fallible (there is no TrySink) but drain() returns a Drain whose Error is Infallible.
And Stream::forward() requires the stream to be fallible (actually TryStream) and have the same error type as the given sink. Your code fails because your error type is String, and that cannot be drained.
The solution, since you are filtering the is_ok results, it to unwrap and rewrap the values:
#[tokio::main]
async fn main() {
futures::stream::iter(0..20)
.map(foo)
.map_ok(|x| x * 10)
.and_then(bar)
.filter(|x| futures::future::ready(x.is_ok()))
.map(|x| Ok(x.unwrap())) // <---- rewrap!
.forward(futures::sink::drain())
.await.unwrap();
}
I feel that there should be an easier way to build a Result<_, Infallible>, but I don't know how. You could write map_err(|_| panic!()) but that is hardly better.
You can use collect::<()>() to run the stream to completion. Example:
use futures::StreamExt;
#[tokio::main]
async fn main() {
futures::stream::iter(0..20)
.map(|i| async move {
// Do something here
println!("{}", i);
})
.buffer_unordered(4)
.collect::<()>()
.await;
}
Although collect::<()>() has the word collect, it does not collect anything or build any data structure. It just loop over the stream and execute to completion.
One thing to note, to use collect::<()>() the Item of your stream must be (). In other words, you must handle both result and error before using this method. I think this make perfect sense.

Future and Stream nesting with type problems

I'd like to use a future which returns a Vec<String>, iterate over this in a future-stream and give the values to another future and the result of this future should be handled. The complete thing should be a future, too.
What's the way to go? I've tried different approaches and with all I've got type problems, which I don't understand.
Why there are these nested future result type signatures? Shouldn't this become the final result? Why doesn't the compiler know the types?
error[E0631]: type mismatch in closure arguments
--> src/lib.rs:45:18
|
45 | .then(|x: Result<(), ()>| ok(()))
| ^^^^ -------------------------- found signature of `fn(std::result::Result<(), ()>) -> _`
| |
| expected signature of `fn(std::result::Result<std::vec::Vec<tokio::prelude::future::Then<tokio::prelude::future::Then<impl tokio::prelude::Future, tokio::prelude::future::FutureResult<(), ()>, [closure#src/lib.rs:35:31: 41:26]>, tokio::prelude::future::FutureResult<(), _>, [closure#src/lib.rs:42:31: 42:57]>>, _>) -> _`
I've setup a Playground for this
extern crate tokio;
use tokio::prelude::future::ok;
use tokio::prelude::*;
#[allow(dead_code)]
pub fn test_future<F>(f: F) -> Result<F::Item, F::Error>
where
F: IntoFuture,
F::Future: Send + 'static,
F::Item: Send + 'static,
F::Error: Send + 'static,
{
let mut runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime");
runtime.block_on(f.into_future())
}
#[allow(dead_code)]
fn fut(el: &String) -> impl Future<Item = String, Error = std::io::Error> {
ok((el.to_string() + "-ok").to_string())
}
#[test]
fn reporting_future_result_test() {
let v = vec![
vec!["a".to_string(), "b".to_string()],
vec!["a".to_string(), "b".to_string()],
];
let f = stream::iter_ok(v.iter().cloned())
.map(|el: Vec<String>| {
stream::iter_ok(el.iter().cloned())
.map(|ell: String| {
fut(&ell)
.then(|x: Result<String, std::io::Error>| {
match x {
Ok(s) => println!("{}", s),
Err(e) => println!("{:?}", e),
};
ok(())
})
.then(|x: Result<(), ()>| ok(()))
})
.collect()
.then(|x: Result<(), ()>| ok(()))
})
.collect()
.then(|x: Result<Vec<_>, std::io::Error>| ok(()));
let r = test_future(f);
match r {
Ok(x) => println!("{:?}", x),
Err(_) => println!("error"),
}
}
extern crate tokio; // 0.1.11
use tokio::prelude::*;
// a future which returns a Vec<String>
fn makes_strings() -> impl Future<Item = Vec<String>, Error = ()> {
future::ok(vec![])
}
fn make_new_string(el: String) -> impl Future<Item = String, Error = ()> {
future::ok(el + "-ok")
}
fn iterate_over() -> impl Future<Item = Vec<String>, Error = ()> {
makes_strings().and_then(|v| {
// iterate over this
let strings = v.into_iter();
// give the values to another future
let futures = strings.map(make_new_string);
// The complete thing should be a future
future::join_all(futures)
})
}
future::ok
Future::and_then
future::join_all

How to do polymorphic IO from either a File or stdin in Rust?

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're willing to restructure you're code a bit, you can actually get away without doing dynamic dispatch. You just need to make sure whatever code is using the reader is wrapped in it's own function and the concrete types of the arguments for that function are known at compile time.
So if we eschew the enum Input idea for a moment, and building on #Yerke's answer, we can do:
use std::env;
use std::fs;
use std::io::{BufRead, BufReader, Read};
fn main() {
let input = env::args().nth(1);
match input {
Some(filename) => output_lines(fs::File::open(filename).unwrap()),
None => output_lines(std::io::stdin()),
};
}
fn output_lines<R: Read>(reader: R) {
let buffer = BufReader::new(reader);
for line in buffer.lines() {
println!("{:?}", line);
}
}
Because we have a concrete type for R each time we call output_lines, the compiler can monomorphize the output_lines function and do static dispatch. In addition to being less complicated code in my opinion (no need for Box wrapping), it's also slightly faster and the compiler can do more optimizations.

Resources