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:

  1. .and_then(|s| s.parse::<i32>()
  2. .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:

  1. use std::error;
  2. use std::fmt;
  3. use std::num::ParseIntError;
  4. // Change the alias to `Box<error::Error>`.
  5. type Result<T> = std::result::Result<T, Box<error::Error>>;
  6. #[derive(Debug)]
  7. struct EmptyVec;
  8. impl fmt::Display for EmptyVec {
  9. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  10. write!(f, "invalid first item to double")
  11. }
  12. }
  13. impl error::Error for EmptyVec {
  14. fn description(&self) -> &str {
  15. "invalid first item to double"
  16. }
  17. fn cause(&self) -> Option<&error::Error> {
  18. // Generic error, underlying cause isn't tracked.
  19. None
  20. }
  21. }
  22. // The same structure as before but rather than chain all `Results`
  23. // and `Options` along, we `?` to get the inner value out immediately.
  24. fn double_first(vec: Vec<&str>) -> Result<i32> {
  25. let first = vec.first().ok_or(EmptyVec)?;
  26. let parsed = first.parse::<i32>()?;
  27. Ok(2 * parsed)
  28. }
  29. fn print(result: Result<i32>) {
  30. match result {
  31. Ok(n) => println!("The first doubled is {}", n),
  32. Err(e) => println!("Error: {}", e),
  33. }
  34. }
  35. fn main() {
  36. let numbers = vec!["42", "93", "18"];
  37. let empty = vec![];
  38. let strings = vec!["tofu", "93", "18"];
  39. print(double_first(numbers));
  40. print(double_first(empty));
  41. print(double_first(strings));
  42. }

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.

See also:

From::from and ?