Reputation: 927
Is there an idiomatic/concise way to convert Result<T, E1>
to Result<T, E2>
when E2
has implemented the From
trait for E1
?
I cannot use the ?
operator because it doesn't compile.
In my case, E1
is ParseIntError
, and E2
is a custom CalcPackageSizeError
error enum.
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..");
}
}
}
This gives a compile error:
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)?
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Though both errors seem semantically similar, I need to respond in different way to both the situations, so I can't make them one.
Upvotes: 3
Views: 2373
Reputation: 430741
Use a combination of 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)."))
}
As Jmb points out in the comments, you could also use Ok
and ?
together:
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)."))
}
The problem is that ?
unwraps the value on success and returns from the function on failure. That means that parse_comma_separated_num(amt_str)?
evaluates to a usize
, as the compiler tells you:
return type inferred to be
usize
here
This would cause the first block to evaluate to a usize
and the second block to evaluate to a Result
. Those aren't the same type, resulting in the error you got.
Converting the error type using map_err
preserves the value as a Result
, allowing both blocks to evaluate to the same type.
See also:
Upvotes: 3