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

3
是否有一种习惯用语/简洁的方法可以将`Result `转换为`Result `,其中E2实现了From trait(特质) 以适应E1
我不能使用?运算符因为它无法编译。
在我的情况下,E1ParseIntError,而E2是自定义的CalcPackageSizeError错误枚举。
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)?
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

虽然这两个错误在语义上看起来相似,但我需要针对每种情况采取不同的回应方式,因此我不能将它们视为一种情况。

2
要么你实现 From <E1> for E2,要么使用 Result::map_err - Netwave
@Netwave,我已经为我的错误类型实现了“From”。不过我会研究一下Result::map_err。 - zombiesauce
让我们在聊天中继续这个讨论 - zombiesauce
2
Ok(parse_comma_separated_num(amt_str)?)parse_comma_separated_num(amt_str).map_err(|e| e.into()) 应该可以解决问题。 - Jmb
@Jmb 谢谢!您可以将其编写为答案,而不是评论,这样我就可以接受它了! - zombiesauce
@Jmb,如果你愿意的话,我很乐意从我的答案中删除Ok(x?)版本。不过当你评论时,我已经在写map_err版本了;-) - Shepmaster
1个回答

3
使用 Result::map_errInto::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)."))
}

正如Jmb在评论中指出的那样,你也可以同时使用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)."))
}

问题在于?在成功时会解包该值并从函数中返回,而在失败时则返回错误。这意味着parse_comma_separated_num(amt_str)?将评估为一个usize,正如编译器所告诉你的那样:

在此处推断的返回类型为usize

这将导致第一个块评估为一个usize,第二个块评估为一个Result。这些不是相同类型,导致了您得到的错误。
使用map_err转换错误类型可将该值保留为Result,使得两个块都可以评估为相同的类型。
另请参见:

哇,我完全忘记了在错误处理中存在 .into()。谢谢! - DexieTheSheep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接