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,
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.
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>,
};
}
}
The coalesce crate provides a way to do this without boxing and in a way that is less verbose than my other answer. The idea is to use a simple enum that can hold the concrete type corresponding to each arm and a macro (coalesce!
) that expands to a match
where the body expression is the same for each arm.
#[macro_use]
extern crate coalesce;
use std::io::{self, Read};
use std::fs::File;
use std::path::Path;
use coalesce::Coalesce2;
fn main() {
if let Some(arg) = std::env::args().nth(1).as_ref() {
let reader = match arg.as_ref() {
"-" => Coalesce2::A(io::stdin()),
path => Coalesce2::B(File::open(&Path::new(path)).unwrap()),
};
let reader = coalesce!(2 => |ref reader| reader as &Read);
// the previous line is equivalent to:
let reader = match reader {
Coalesce2::A(ref reader) => reader as &Read,
Coalesce2::B(ref reader) => reader as &Read,
};
}
}
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>,
};
}
}