How do I overcome match arms with incompatible types for structs implementing same trait?

后端 未结 4 656
忘掉有多难
忘掉有多难 2020-11-29 11:59

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,          


        
相关标签:
4条回答
  • 2020-11-29 12:29

    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.

    0 讨论(0)
  • 2020-11-29 12:33

    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>,
            };
        }
    }
    
    0 讨论(0)
  • 2020-11-29 12:41

    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,
            };
        }
    }
    
    0 讨论(0)
  • 2020-11-29 12:43

    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>,
            };
        }
    }
    
    0 讨论(0)
提交回复
热议问题