reading from a file or stdin in Rust
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();
}