【问题标题】:How to do polymorphic IO from either a File or stdin in Rust?如何从 Rust 中的 File 或 stdin 进行多态 IO?
【发布时间】:2016-07-05 10:16:39
【问题描述】:

我正在尝试实现一个“多态”Input 枚举,它隐藏了我们是从文件中读取,还是从标准输入中读取。更具体地说,我正在尝试构建一个具有 lines 方法的枚举,该方法将依次“委托”调用包装成 FileBufReaderStdInLock (两者都有lines() 方法)。

这是枚举:

enum Input<'a> {
    Console(std::io::StdinLock<'a>),
    File(std::io::BufReader<std::fs::File>)
}

我有三种方法:

  • from_arg 通过检查是否提供了参数(文件名)来决定我们是从文件中读取还是从标准输入中读取,
  • file 用于使用 BufReader 包装文件,
  • console 用于锁定标准输入。

实现:

impl<'a> Input<'a> {
    fn console() -> Input<'a> {
        Input::Console(io::stdin().lock())
    }

    fn file(path: String) -> io::Result<Input<'a>> {
        match File::open(path) {
            Ok(file) => Ok(Input::File(std::io::BufReader::new(file))),
            Err(_) => panic!("kita"),
        }
    }

    fn from_arg(arg: Option<String>) -> io::Result<Input<'a>> {
        Ok(match arg {
            None => Input::console(),
            Some(path) => try!(Input::file(path)),
        })
    }
}

据我了解,我必须同时实现 BufReadRead 特征才能使其工作。这是我的尝试:

impl<'a> io::Read for Input<'a> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match *self {
            Input::Console(ref mut c) => c.read(buf),
            Input::File(ref mut f) => f.read(buf),
        }
    }
}

impl<'a> io::BufRead for Input<'a> {
    fn lines(self) -> Lines<Self> {
        match self {
            Input::Console(ref c) => c.lines(),
            Input::File(ref f) => f.lines(),
        }
    }

    fn consume(&mut self, amt: usize) {
        match *self {
            Input::Console(ref mut c) => c.consume(amt),
            Input::File(ref mut f) => f.consume(amt),
        }
    }

    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        match *self {
            Input::Console(ref mut c) => c.fill_buf(),
            Input::File(ref mut f) => f.fill_buf(),
        }
    }
}

最后,调用:

fn load_input<'a>() -> io::Result<Input<'a>> {
    Ok(try!(Input::from_arg(env::args().skip(1).next())))
}

fn main() {
    let mut input = match load_input() {
        Ok(input) => input,
        Err(error) => panic!("Failed: {}", error),
    };

    for line in input.lines() { /* do stuff */ }
}

Complete example in the playground

编译器告诉我我的模式匹配错误并且我有mismatched types

error[E0308]: match arms have incompatible types
  --> src/main.rs:41:9
   |
41 | /         match self {
42 | |             Input::Console(ref c) => c.lines(),
   | |                                      --------- match arm with an incompatible type
43 | |             Input::File(ref f) => f.lines(),
44 | |         }
   | |_________^ expected enum `Input`, found struct `std::io::StdinLock`
   |
   = note: expected type `std::io::Lines<Input<'a>>`
              found type `std::io::Lines<std::io::StdinLock<'_>>`

我试图满足它:

match self {
    Input::Console(std::io::StdinLock(ref c)) => c.lines(),
    Input::File(std::io::BufReader(ref f)) => f.lines(),
}

...但这也不起作用。

看来,我在这方面实在是太过分了。

【问题讨论】:

标签: io polymorphism rust


【解决方案1】:

@A.B. 的回答是正确的,但它试图符合 OP 的原始程序结构。我想为偶然发现这个问题的新人提供一个更具可读性的替代方案(就像我一样)。

use std::env;
use std::fs;
use std::io::{self, BufReader, BufRead};

fn main() {
    let input = env::args().nth(1);
    let reader: Box<dyn BufRead> = match input {
        None => Box::new(BufReader::new(io::stdin())),
        Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap()))
    };
    for line in reader.lines() {
        println!("{:?}", line);
    }
}

查看reddit 中的讨论,我从中借用了代码。

注意盒装 BufRead 之前的 dyn 关键字。这种模式称为trait object

【讨论】:

    【解决方案2】:

    这是最简单的解决方案,但会借用并锁定Stdin

    use std::fs::File;
    use std::io::{self, BufRead, Read};
    
    struct Input<'a> {
        source: Box<BufRead + 'a>,
    }
    
    impl<'a> Input<'a> {
        fn console(stdin: &'a io::Stdin) -> Input<'a> {
            Input {
                source: Box::new(stdin.lock()),
            }
        }
    
        fn file(path: &str) -> io::Result<Input<'a>> {
            File::open(path).map(|file| Input {
                source: Box::new(io::BufReader::new(file)),
            })
        }
    }
    
    impl<'a> Read for Input<'a> {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
            self.source.read(buf)
        }
    }
    
    impl<'a> BufRead for Input<'a> {
        fn fill_buf(&mut self) -> io::Result<&[u8]> {
            self.source.fill_buf()
        }
    
        fn consume(&mut self, amt: usize) {
            self.source.consume(amt);
        }
    }
    

    由于默认的 trait 方法,ReadBufRead 完全为 Input 实现。所以你可以在Input上拨打lines

    let input = Input::file("foo.txt").unwrap();
    for line in input.lines() {
        println!("input line: {:?}", line);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-22
      • 2019-10-09
      • 1970-01-01
      • 1970-01-01
      • 2019-12-05
      • 2014-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多