Your command-line application reads from a file path supplied by the user and in an effort to be maximally useful, you've decided it should have the ability to read from stdin (when the user provides - as the file path).

Given the following path/to/file.csv

date,amount,memo
2020-12-14,-500.00,PS5 from Joe's Game Center

and either one of these invocations

$ ./your-program path/to/file.csv

# OR

$ cat path/to/file.csv | ./your-program -

you want to see all of the lines printed to stdout (yep, implementing a cat-like while using cat in the example 🤷).


Given both io::stdin and fs::File::open return types that can be read from (this is known because the types (after error handing) implement io::Read), you may expect to be able to do something like the following.

use std::{env, io, fs};

fn main() {
  // file path provided by user
  let input = env::args().nth(1).unwrap();

  // get the thing we can read from
  let mut rdr = match input.as_ref() {
    "-" => io::stdin(),
    _   => fs::File::open(input).unwrap(),
  };

  // write data from reader to stdout
  io::copy(&mut rdr, &mut io::stdout()).unwrap();
}

But this will fail to compile!

error[E0308]: `match` arms have incompatible types
  --> src/lib.rs:10:12
   |
8  |     let mut rdr = match input.as_ref() {
   |  _________________-
9  | |     "-" => io::stdin(),
   | |            ----------- this is found to be of type `Stdin`
10 | |     _   => fs::File::open(input).unwrap(),
   | |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Stdin`, found struct `File`
11 | |   };
   | |___- `match` arms have incompatible types

error: aborting due to previous error

The compiler cannot determine the appropriate type for rdr since in one arm it is being assigned to a Stdin and in the other a File. What is needed here is a type that allows the post-reading code to be agnostic to the source of the data (file, stdin, socket, ...).

Box<io::Read> is this story's hero. 3 quick changes (all Box-related) et voilà!

use std::{env, io, fs};

fn main() {
  // file path provided by user
  let input = env::args().nth(1).unwrap();

  // get the generic thing we can read from
  let mut rdr: Box<io::Read> = match input {
    "-" => Box::new(io::stdin()),
    _   => Box::new(fs::File::open(input).unwrap()),
  };

  // write data from reader to stdout (or whatever you do with it)
  io::copy(&mut rdr, &mut io::stdout()).unwrap();
}