9. Error Handling

Learning objectives

  • Understand the 2 types of errors in Rust
  • Describe when to use what type of error
  • Know how to work with a Result enum

Intro

Types of errors

  • recoverable
  • unrecoverable

No exceptions

  • Result<T, E>
  • panic!

panic!

  • panic!("message")
    • prints the message
    • cleans up the stack
    • stops the program
  • Set RUST_BACKTRACE = 1 to print a backtrace
  • Set panic = 'abort' in Cargo.toml to quit without cleaning the stack

Result

Recoverable errors

enum Result<T, E> {
    Ok(T),
    Err(E),
}
  • T is the type of the returned value on success
  • E is the type of the error on failure

Recoverable error - reading a file

use std::fs::File;
fn main() {
    let greeting_file_result = File::open("hello.txt");
}
  • T = std::fs::File
  • E = std::io::Error

Recoverable error - reading a file

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {error:?}"),
    };
}
  • success (Ok) -> std::fs::File
  • error (Err) -> close the program

Recoverable error - reading a file

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {e:?}"),
            },
            other_error => {
                panic!("Problem opening the file: {other_error:?}");
            }
        },
    };
}
  • success (Ok) -> std::fs::File
  • error (Err) ->
    • file not found error (ErrorKind::NotFound) -> create file
      • success (Ok) -> std::fs::File
      • error -> close the program
    • other error type -> close the program

panic! on error shortcuts

unwrap

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
}
  • Ok -> value
  • Err -> panic!

expect

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")
        .expect("hello.txt should be included in this project");
}
  • Ok -> value
  • Err -> panic!
    • panic!("message")

Propagating errors

  • return error
  • calling code decides what to do
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

Propagating errors - shortcut

? returns value or returns error (early)

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;

    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

When to use ?

  • Can’t always use it
  • Return type of function must be compatible with type returned by function ? is used on
    • Ex: outer function returns Result, and inner function returns Result

Can’t use ?

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")?;
}
  • main returns ()
  • File::open returns Result

Result or panic!?

Result

  • Calling code should choose what to do
  • Failure is a possibility, but not a dealbreaker

panic!

  • Calling code should not have a choice
  • Safety or correctness not guaranteed
  • Examples, prototypes, tests