Other uses of ?
Notice in the previous example that our immediate reaction to calling parse
is to map
the error from a library error into a boxed error:
.and_then(|s| s.parse::<i32>())
.map_err(|e| e.into())
Since this is a simple and common operation, it would be convenient if it could be elided. Alas, because and_then
is not sufficiently flexible, it cannot. However, we can instead use ?
.
?
was previously explained as either unwrap
or return Err(err)
. This is only mostly true. It actually means unwrap
or return Err(From::from(err))
. Since From::from
is a conversion utility between different types, this means that if you ?
where the error is convertible to the return type, it will convert automatically.
Here, we rewrite the previous example using ?
. As a result, the map_err
will go away when From::from
is implemented for our error type:
use std::error;
use std::fmt;
// Change the alias to `Box<dyn error::Error>`.
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;
#[derive(Debug)]
struct EmptyVec;
impl fmt::Display for EmptyVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}
impl error::Error for EmptyVec {}
// The same structure as before but rather than chain all `Results`
// and `Options` along, we `?` to get the inner value out immediately.
fn double_first(vec: Vec<&str>) -> Result<i32> {
let first = vec.first().ok_or(EmptyVec)?;
let parsed = first.parse::<i32>()?;
Ok(2 * parsed)
}
fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];
print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}
This is actually fairly clean now. Compared with the original panic
, it is very similar to replacing the unwrap
calls with ?
except that the return types are Result
. As a result, they must be destructured at the top level.