当问号运算符无效时,如何将 Result<T, E1> 转换为 Result<T, E2>?

How can I convert a Result<T, E1> to Result<T, E2> when the question mark operator is ineffective?

E2E1 实现了 From 特性时,是否有 idiomatic/concise 方法将 Result<T, E1> 转换为 Result<T, E2>

我无法使用 ? 运算符,因为它无法编译。

在我的例子中,E1ParseIntErrorE2 是自定义的 CalcPackageSizeError 错误枚举。

playground

use std::error;
use std::fmt;
use std::io;
use std::io::Read;
use std::num::ParseIntError;

#[derive(Debug)]
enum CalcPackageSizeError {
    InvalidInput(&'static str),
    BadNum(&'static str),
}
impl error::Error for CalcPackageSizeError {}
impl fmt::Display for CalcPackageSizeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match *self {
                Self::InvalidInput(err_desc) => err_desc,
                Self::BadNum(err_desc) => err_desc,
            }
        )
    }
}

impl From<ParseIntError> for CalcPackageSizeError {
    fn from(_: ParseIntError) -> Self {
        CalcPackageSizeError::BadNum(
            "Error in calculating size of one or more of the packages involved.",
        )
    }
}

fn parse_comma_separated_num(num_str: &str) -> Result<usize, ParseIntError> {
    num_str
        .chars()
        .filter(|char| *char != ',')
        .collect::<String>()
        .parse::<usize>()
}

fn calc_all_package_size(contents: &str) -> Result<usize, CalcPackageSizeError> {
    contents
        .split('\n')
        .skip(2)
        .map(|package_str| {
            let amount_str = package_str
                .split(' ')
                .filter(|element| *element != "")
                .nth(1);

            if let Some(amt_str) = amount_str {
                parse_comma_separated_num(amt_str)?
                // match parse_comma_separated_num(amt_str) {
                //     Ok(amt) => Ok(amt),
                //     Err(err) => Err(From::from(err)),
                // }
            } else {
                Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
            }
        })
        .sum()
}

fn main() {
    let mut wajig_input = String::from(
        "Package                           Size (KB)        Status
=================================-==========-============
geoip-database                      10,015      installed
aptitude-common                     10,099      installed
ieee-data                           10,137      installed
hplip-data                          10,195      installed
librsvg2-2                          10,412      installed
fonts-noto-color-emoji              10,610      installed",
    );
    // io::stdin().read_to_string(&mut wajig_input).expect("stdin io rarely fails.");
    match calc_all_package_size(wajig_input.as_str()) {
        Ok(total_size_in_kb) => {
            let size_in_mb = total_size_in_kb as f64 / 1024.0;
            println!("Total size of packages installed: {} MB", size_in_mb);
        }
        Err(error) => {
            println!("Oops! Encountered some error while calculating packages' size.");
            println!("Here's the error: \n {}", error);
            println!("\n-- Gracefully exiting..");
        }
    }
}

这给出了一个编译错误:

error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:59:17
   |
52 | /             if let Some(amt_str) = amount_str {
53 | |                 parse_comma_separated_num(amt_str)?
   | |                 ----------------------------------- expected because of this
54 | |                 // match parse_comma_separated_num(amt_str) {
55 | |                 //     Ok(amt) => Ok(amt),
...  |
59 | |                 Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
   | |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found enum `Result`
60 | |             }
   | |_____________- `if` and `else` have incompatible types
   |
   = note: expected type `usize`
              found enum `Result<_, CalcPackageSizeError>`
note: return type inferred to be `usize` here
  --> src/main.rs:53:17
   |
53 |                 parse_comma_separated_num(amt_str)?
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

虽然这两个错误在语义上看起来很相似,但我需要对这两种情况做出不同的反应,所以我不能把它们合二为一。

使用 Result::map_err and Into::into:

的组合
if let Some(amt_str) = amount_str {
    parse_comma_separated_num(amt_str).map_err(Into::into)
} else {
    Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
}

作为,你也可以同时使用Ok?

if let Some(amt_str) = amount_str {
    Ok(parse_comma_separated_num(amt_str)?)
} else {
    Err(CalcPackageSizeError::InvalidInput("Input not as expected, expected the 2nd spaces-delimited item to be the size (integer)."))
}

问题是 ? 在失败时从函数中解包成功 和 returns 的值。这意味着 parse_comma_separated_num(amt_str)? 的计算结果为 usize,正如编译器告诉您的那样:

return type inferred to be usize here

这将导致第一个块的计算结果为 usize,第二个块的计算结果为 Result。这些不是同一类型,导致您收到错误。

使用 map_err 转换错误类型会将值保留为 Result,允许两个块评估为同一类型。

另请参阅: