如何将 io::Result 转换为 anyhow::Result?

时间:2021-05-12 15:11:09

标签: error-handling rust

我从这个工作代码开始:

use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Result};
use std::path::Path;

fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
where
    P: AsRef<Path>,
{
    let handles = files.into_iter().map(|path| File::open(path).unwrap());
    handles.flat_map(|handle| BufReader::new(handle).lines())
}

fn main() -> Result<()> {
    let lines = read_lines(env::args().skip(1).collect::<Vec<String>>());
    for line in lines {
        println!("{:?}", line?)
    }

    Ok(())
}

我需要将其集成到严重依赖 anyhow 库的代码库中,但我不知道如何将 BufReader::lines 中的 flatmap 返回值转换为impl Iterator<Item = anyhow::Result<String>>

作为我遇到问题的可重现示例,我将 anyhow 与此 Cargo.toml 集成到我的测试台中,

[package]
name = "rust-playground"
version = "0.1.0"
authors = ["Charles"]
edition = "2018"

[dependencies]
anyhow = "1"

我用 std::io::Result 替换了 anyhow::Result 的导入。我不确定在哪里放置 with_context 调用,我尝试过的所有方法都导致了编译器错误。

此尝试失败,因为我不能在闭包内使用 ?,但我还能如何“解开”?我不允许在这种情况下使用 unwrap,我应该以某种方式返回 anyhow::Result...

use anyhow::{Context, Result};
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;

fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
where
    P: AsRef<Path>,
{
    let handles = files.into_iter().map(|path| {
        File::open(path).with_context(|| format!("opening path: {}", path.as_ref().display()))?
    });
    handles.flat_map(|handle| BufReader::new(handle).lines())
}

fn main() -> Result<()> {
    let lines = read_lines(env::args().skip(1).collect::<Vec<String>>());
    for line in lines {
        println!("{:?}", line?)
    }

    Ok(())
}

和错误信息:

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `Try`)
  --> src/main.rs:13:10
   |
12 |       files.into_iter().map(|path|
   |  ___________________________-
13 | |                   File::open(path).with_context(|| format!("opening path: {}", path.as_ref().display()))?);
   | |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
   | |___________________|_____________________________________________________________________________________|
   |                     |                                                                                     this function should return `Result` or `Option` to accept `?`                                                     
   |                     cannot use the `?` operator in a closure that returns `File`
   |
   = help: the trait `Try` is not implemented for `File`
   = note: required by `from_error`

error[E0271]: type mismatch resolving `<FlatMap<Map<<I as IntoIterator>::IntoIter, [closure@src/main.rs:12:24: 13:97]>, std::io::Lines<BufReader<File>>, [closure@src/main.rs:14:22: 14:61]> as Iterator>::Item == std::result::Result<String, anyhow::Error>`
 --> src/main.rs:7:58
  |
7 | fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
  |                                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::io::Error`, found struct `anyhow::Error`                                                     
  |
  = note: expected enum `std::result::Result<_, std::io::Error>`
             found enum `std::result::Result<_, anyhow::Error>`

如果我的方法处理单个文件名,我可以想出一种方法来进行编译:

fn read_lines<P>(filename: P) -> Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(&filename)
        .with_context(|| format!("opening filename: {}", filename.as_ref().display()))?;
    Ok(BufReader::new(file).lines())
}

但这并不能正确地推广到处理多个文件名的情况,因为 File::open(&path).with_context(...)? 在迭代器中是不正确的。

1 个答案:

答案 0 :(得分:1)

您提供的用于处理单个文件的最后一个片段似乎与前面的示例返回的内容不同。

如果您只想为多个文件扩展单个文件示例,那么您可以将单文件函数映射到文件列表上。

像这样

fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<std::io::Lines<BufReader<File>>>>
where
    P: AsRef<Path>,
{
    
    files.into_iter().map(|filename: P| {
        let file = File::open(&filename)
            .with_context(|| format!("opening filename: {}", filename.as_ref().display()))?;

        Ok(BufReader::new(file).lines())
    })
}

但是如果您希望函数返回 Result<String> 而不是 Result<std::io::Lines<BufReader<File>>>,您可以像这样将错误从 io::Error 转换为 anyhow::Error

fn read_lines<P, I: IntoIterator<Item = P>>(files: I) -> impl Iterator<Item = Result<String>>
where
    P: AsRef<Path>,
{
    files.into_iter().flat_map(|filename: P| {
        let file = File::open(&filename)
            .with_context(|| format!("opening filename: {}", filename.as_ref().display()));

        file.into_iter().flat_map(|file| {
            BufReader::new(file)
                .lines()
                .map(|line| line.map_err(anyhow::Error::from))
        })
    })
}