Try Conversions

The effective expansion of ? is a little more complicated than previously indicated:

  1. expression?

works the same as

  1. match expression {
  2. Ok(value) => value,
  3. Err(err) => return Err(From::from(err)),
  4. }

The From::from call here means we attempt to convert the error type to the type returned by the function. This makes it easy to encapsulate errors into higher-level errors.

Example

  1. use std::error::Error;
  2. use std::io::Read;
  3. use std::{fmt, fs, io};
  4. #[derive(Debug)]
  5. enum ReadUsernameError {
  6.     IoError(io::Error),
  7.     EmptyUsername(String),
  8. }
  9. impl Error for ReadUsernameError {}
  10. impl fmt::Display for ReadUsernameError {
  11.     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  12.         match self {
  13.             Self::IoError(e) => write!(f, "I/O error: {e}"),
  14.             Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
  15.         }
  16.     }
  17. }
  18. impl From<io::Error> for ReadUsernameError {
  19.     fn from(err: io::Error) -> Self {
  20.         Self::IoError(err)
  21.     }
  22. }
  23. fn read_username(path: &str) -> Result<String, ReadUsernameError> {
  24.     let mut username = String::with_capacity(100);
  25.     fs::File::open(path)?.read_to_string(&mut username)?;
  26.     if username.is_empty() {
  27.         return Err(ReadUsernameError::EmptyUsername(String::from(path)));
  28.     }
  29.     Ok(username)
  30. }
  31. fn main() {
  32.     //std::fs::write("config.dat", "").unwrap();
  33.     let username = read_username("config.dat");
  34.     println!("username or error: {username:?}");
  35. }

This slide should take about 5 minutes.

The ? operator must return a value compatible with the return type of the function. For Result, it means that the error types have to be compatible. A function that returns Result<T, ErrorOuter> can only use ? on a value of type Result<U, ErrorInner> if ErrorOuter and ErrorInner are the same type or if ErrorOuter implements From<ErrorInner>.

A common alternative to a From implementation is Result::map_err, especially when the conversion only happens in one place.

There is no compatibility requirement for Option. A function returning Option<T> can use the ? operator on Option<U> for arbitrary T and U types.

A function that returns Result cannot use ? on Option and vice versa. However, Option::ok_or converts Option to Result whereas Result::ok turns Result into Option.