Try Operator

Runtime errors like connection-refused or file-not-found are handled with the Result type, but matching this type on every call can be cumbersome. The try-operator ? is used to return errors to the caller. It lets you turn the common

1. match some_expression {
2. Ok(value) => value,
3. Err(err) => return Err(err),
4. }

into the much simpler

1. some_expression?

We can use this to simplify our error handling code:

1. ```
   use std::io::Read;
   ```
2. ```
   use std::{fs, io};
   ```

4. ```
   fn read_username(path: &str) -> Result<String, io::Error> {
   ```
5. ```
       let username_file_result = fs::File::open(path);
   ```
6. ```
       let mut username_file = match username_file_result {
   ```
7. ```
           Ok(file) => file,
   ```
8. ```
           Err(err) => return Err(err),
   ```
9. ```
       };
   ```

11. ```
        let mut username = String::new();
    ```
12. ```
        match username_file.read_to_string(&mut username) {
    ```
13. ```
            Ok(_) => Ok(username),
    ```
14. ```
            Err(err) => Err(err),
    ```
15. ```
        }
    ```
16. ```
    }
    ```

18. ```
    fn main() {
    ```
19. ```
        //fs::write("config.dat", "alice").unwrap();
    ```
20. ```
        let username = read_username("config.dat");
    ```
21. ```
        println!("username or error: {username:?}");
    ```
22. ```
    }
    ```

This slide should take about 5 minutes.

Simplify the read_username function to use ?.

Key points:

  • The username variable can be either Ok(string) or Err(error).
  • Use the fs::write call to test out the different scenarios: no file, empty file, file with username.
  • Note that main can return a Result<(), E> as long as it implements std::process::Termination. In practice, this means that E implements Debug. The executable will print the Err variant and return a nonzero exit status on error.