Как мне преодолеть спички с несовместимыми типами для структур, реализующих ту же черту?
Я пытаюсь написать cat
команда для изучения Rust, но я не могу преобразовать аргументы командной строки в структуры чтения.
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(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
Не похоже, что можно полиморфно сопоставлять реализации свойств (связанные). Как я могу использовать либо File
или же Stdin
как читатель?
5 ответов
Проблема в том, что 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>,
};
}
}
Вот вариант ответа Лукаса, который избегает бокса:
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
привязки, которые инициализируются только в некоторых путях кода, но при этом имеют достаточно длительный срок службы, чтобы иметь возможность использовать их в качестве цели заимствованного указателя.
Принятый ответ не работает с Rustv1.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>,
};
}
}
coalesce
клеть обеспечивает способ сделать это без бокса и таким способом, который является менее многословным, чем мой другой ответ. Идея состоит в том, чтобы использовать простое перечисление, которое может содержать конкретный тип, соответствующий каждой руке, и макрос (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,
};
}
}
Вариант ответа @FrancisGagné : в то время какcoalesce
ящик является более общим, в данном случае either
также будет выполняться и будет немного менее подробным:
use std::{env, fs::File, io};
use either::Either;
fn main() {
for arg in env::args().skip(1) {
let mut reader = match arg.as_str() {
"-" => Either::Right(io::stdin()),
path => Either::Left(File::open(&path).unwrap()),
};
// just for show: Either implements Read!
let reader: &mut dyn io::Read = &mut reader;
}
}