我正在尝试编写cat
命令来学习Rust,但我似乎无法将命令行参数转换为reader结构。
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[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(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
似乎不可能多态匹配特质实施者(related)。如何使用File
或Stdin
作为读者?
答案 0 :(得分:14)
问题是stdin()
返回类型为Stdio
的对象,而File::open(...).unwrap()
返回类型为File
的对象。在Rust中,匹配的所有臂都必须返回相同类型的值。
在这种情况下,您可能想要返回一个公共Read
对象。不幸的是Read
是一个特性,所以你不能按值传递它。最简单的选择是采用堆分配:
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>,
};
}
}
答案 1 :(得分:10)
接受的答案不再适用于 Rust v1.0
。主要陈述仍然是正确的:比赛武器必须返回相同的类型。在堆上分配对象可以解决问题。
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>,
};
}
}
答案 2 :(得分:10)
这是Lukas's答案的变体,可以避免装箱:
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
}
};
}
}
这里的技巧是使用仅在某些代码路径上初始化的let
绑定,同时仍然具有足够长的生命周期,以便能够将它们用作借用指针的目标。
答案 3 :(得分:4)
coalesce
crate提供了一种方法,可以在没有装箱的情况下以比my other answer更简洁的方式执行此操作。我们的想法是使用一个简单的枚举,它可以保存与每个手臂相对应的具体类型,以及一个扩展为coalesce!
的宏(match
),其中每个手臂的身体表达相同。
#[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,
};
}
}