对于实现相同特征的结构,如何克服具有不兼容类型的匹配臂?

AJc*_*dez 12 rust

我正在尝试编写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(),
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

错误:

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(),
  |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

似乎不可能多态匹配特质实施者(相关).我如何使用a FileStdin作为读者?

PEP*_*EPP 18

问题是stdin()返回一个类型的对象StdioFile::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>,
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢你"匹配的所有武器必须返回相同类型的值" - 我在文档中找不到它. (2认同)

Luk*_*odt 12

接受的答案不再适用于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>,
        };
    }
}
Run Code Online (Sandbox Code Playgroud)


Fra*_*gné 11

这是Lukas的答案的一个变种,避免拳击:

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
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的技巧是使用let仅在某些代码路径上初始化的绑定,同时仍具有足够长的生命周期,以便能够将它们用作借用指针的目标.


Fra*_*gné 6

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,
        };
    }
}
Run Code Online (Sandbox Code Playgroud)