I am attempting to write the cat
command to learn Rust, but I can't seem to convert command line arguments into reader structs.
use std::{env, io};
use std::fs::File;
fn main() {
for arg in env::args().skip(1) {
let reader = match arg.as_str() {
"-" => io::stdin(),
path => File::open(&path).unwrap(),
};
}
}
Error:
error[E0308]: match arms have incompatible types
--> src/main.rs:6:22
|
6 | let reader = match arg.as_str() {
| ^ expected struct `std::io::Stdin`, found struct `std::fs::File`
|
= note: expected type `std::io::Stdin`
= note: found type `std::fs::File`
note: match arm with an incompatible type
--> src/main.rs:8:21
|
8 | path => File::open(&path).unwrap(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
It doesn't seem like it's possible to polymorphically match trait implementers (related). How can I use either a File
or Stdin
as a reader?
The problem is that stdin()
returns an object of type Stdio
and File::open(...).unwrap()
returns an object of type File
. In Rust, all arms of a match have to return values of the same type.
In this case you probably wanted to return a common Read
object. Unfortunately Read
is a trait so you cannot pass it by value. The easiest alternative is to resort to heap allocation:
use std::{env, io};
use std::io::prelude::*;
use std::fs::File;
fn main() {
for arg in env::args().skip(1) {
let reader = match arg.as_str() {
"-" => Box::new(io::stdin()) as Box<Read>,
path => Box::new(File::open(&path).unwrap()) as Box<Read>,
};
}
}
The accepted answer does not work with Rust v1.0
anymore. The main statement is still true though: Match arms have to return the same types. Allocating the objects on the heap solves the problem.
use std::io::{self, Read};
use std::fs::File;
use std::path::Path;
fn main() {
if let Some(arg) = std::env::args().nth(1).as_ref() {
let reader = match arg.as_ref() {
"-" => Box::new(io::stdin()) as Box<Read>,
path => Box::new(File::open(&Path::new(path)).unwrap()) as Box<Read>,
};
}
}
Here's a variation on Lukas's answer that avoids boxing:
use std::io::{self, Read};
use std::fs::File;
use std::path::Path;
fn main() {
if let Some(arg) = std::env::args().nth(1).as_ref() {
let stdin;
let file;
let reader = match arg.as_ref() {
"-" => {
stdin = io::stdin();
&stdin as &Read
}
path => {
file = File::open(&Path::new(path)).unwrap();
&file as &Read
}
};
}
}
The trick here is to use let
bindings that are only initialized on some code paths, while still having a long enough lifetime to be able to use them as the target of a borrowed pointer.
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