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};
   ```

5. ```
   #[derive(Debug)]
   ```
6. ```
   enum ReadUsernameError {
   ```
7. ```
       IoError(io::Error),
   ```
8. ```
       EmptyUsername(String),
   ```
9. ```
   }
   ```

11. ```
    impl Error for ReadUsernameError {}
    ```

13. ```
    impl fmt::Display for ReadUsernameError {
    ```
14. ```
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    ```
15. ```
            match self {
    ```
16. ```
                Self::IoError(e) => write!(f, "I/O error: {e}"),
    ```
17. ```
                Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
    ```
18. ```
            }
    ```
19. ```
        }
    ```
20. ```
    }
    ```

22. ```
    impl From<io::Error> for ReadUsernameError {
    ```
23. ```
        fn from(err: io::Error) -> Self {
    ```
24. ```
            Self::IoError(err)
    ```
25. ```
        }
    ```
26. ```
    }
    ```

28. ```
    fn read_username(path: &str) -> Result<String, ReadUsernameError> {
    ```
29. ```
        let mut username = String::with_capacity(100);
    ```
30. ```
        fs::File::open(path)?.read_to_string(&mut username)?;
    ```
31. ```
        if username.is_empty() {
    ```
32. ```
            return Err(ReadUsernameError::EmptyUsername(String::from(path)));
    ```
33. ```
        }
    ```
34. ```
        Ok(username)
    ```
35. ```
    }
    ```

37. ```
    fn main() {
    ```
38. ```
        //std::fs::write("config.dat", "").unwrap();
    ```
39. ```
        let username = read_username("config.dat");
    ```
40. ```
        println!("username or error: {username:?}");
    ```
41. ```
    }
    ```

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.