1
votes

From the book "Rust in Action" (Manning, currently MEAP), the final example of Chapter 2 shows a way to implement a generic function that can work on BufRead + Sized parameters:

fn process_lines<T: BufRead + Sized>(reader: T, re: Regex) {
  for line_ in reader.lines() {
    let line = line_.unwrap();
    match re.find(&line) {
        Some(_) => println!("{}", line),
        None => (),
    }
  }
}

The code that uses it looks like:

let input = args.value_of("input").unwrap_or("-");
if input == "-" {
  let stdin = io::stdin();
  let reader = stdin.lock();
  process_lines(reader, re);
} else {
  let f = File::open(input).unwrap();
  let reader = BufReader::new(f);
  process_lines(reader, re);
}

I'm wondering if it is possible to declare the reader variable earlier with a generic type so the process_lines() call can be factored out?

let reader: ????;
let input = args.value_of("input").unwrap_or("-");
if input == "-" {
  let stdin = io::stdin();
  reader = stdin.lock();
} else {
  let f = File::open(input).unwrap();
  reader = BufReader::new(f);
}
process_lines(reader, re);

Or by using an (anonymous) method that would return impl BufRead + Sized ?

Something like:

fn build_reader(input: &str) -> impl BufRead + Sized {
  if input == "-" {
    let stdin = io::stdin();
    stdin.lock()  
  } else {
    let f = File::open(input).unwrap();
    BufReader::new(f)
  }
}

But compiler is not very happy, see https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0e74d3e96de0bbf61b9f61d44d305e5e

Edit

I succeeded building a BufRead builder using the advice from https://stackoverflow.com/a/49964042/258622

fn build_reader(input: &str) -> Box<dyn BufRead> {
  if input == "-" { 
    Box::new(BufReader::new(io::stdin()))
  } else {
    let f = File::open(input).unwrap();
    Box::new(BufReader::new(f))
  }
}

Compiler is happy, I wonder what are the consequences of not using stdin.lock()...

Main now looks like:

fn main() {
 ...
 let input = args.value_of("input").unwrap_or("-");
 let reader = build_reader(input);
 process_lines(reader, re)
}

Code that compiles: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c5218ef4a266597da6a36c21e060bcda

Edit 2 : A more idiomatic version taking advantage of the fact that clap::ArgMatches::value_of() returns an Option<&str>:

fn build_reader(input: Option<&str>) -> Box<dyn BufRead> {
  match input {
    None => Box::new(BufReader::new(io::stdin())),
    Some(filename) => {
      let f = File::open(filename).unwrap();
      Box::new(BufReader::new(f))
    }
  }
}

fn main() {
  ...
  let input = args.value_of("input");
  let reader = build_reader(input);
  process_lines(reader, re)
}
2
Why would you want to do that? Also, your solution cannot work because generics and impl Trait can only be one type at a time. They can't be chosen at runtime.mcarton
@mcarton for educational purposesZeDalaye

2 Answers

1
votes

As @sshashank124 has stated, returning impl SomeTrait only means the function is to return one concrete type that implements SomeTrait. It doesn't give the function license to return heterogeneous types.

The fact that Stdin::lock takes a reference also doesn't help here. It will go out of scope as soon as the function returns or the local scope which it is in ends.

Ignore the issue of Stdin::lock for a moment, you can do:

let stdin = io::stdin();
let reader: Box<dyn BufRead> = if input == "-" {
    Box::new(stdin.lock())
} else {
    let f = File::open(input).unwrap();
    Box::new(BufReader::new(f))
};
process_lines(reader, re);

Or with third party crate such as either:

use either::Either;

let stdin = io::stdin();
let reader = if input == "-" {
    Either::Left(stdin.lock())
} else {
    let f = File::open(input).unwrap();
    Either::Right(BufReader::new(f))
};
process_lines(reader, re);

It seems trivial in this case. But it may help you in the future.

0
votes

Firstly, the method approach (with impl BufRead) nor the if/else approach will work since the types of the two BufRead implementers are different (namely StdinLock and BufReader). The issue with both of these methods is that they expect a single concrete type. I wouldn't worry too much about trying to factor out something as trivial as this.

In general however, if you wish to have polymorphism at runtime, you can use the Box<dyn Trait> pattern to create a trait object. More information on this can be found in the rust book here: https://doc.rust-lang.org/1.30.0/book/2018-edition/ch17-02-trait-objects.html