# Error Handling

Rust does not have exceptions. Instead, it uses:

  • Result<T, E> type for recoverable errors
  • panic! macro for unrecoverable errors

.NET Analogy

panic!s are like unhandled exceptions in .NET, while Result allows us to act similarly to try-catch in .NET.

# Panics

We can call panic! when there is no way out of the problem. It terminates the program.

fn main() {
  panic!("fatal error");
}

Unwind or abort

When a panic occurs Rust unwinds the stack - cleans data from all stack frames. It takes time. We can set our app to just abruptly abort execution in case of panic (in the TOML file).

# Call stack

By default, when panic occurs we'll only see the line in our code that led to the panic. We can see the whole callstack by setting the RUST_BACKTRACE environment variable to anything other than 0.

# Recoverable Errors

Our functins might return Result if there is a chance of failure. It's brough in by the prelude.

enum Result<T, E> {
  Ok(T),
  Err(E),
}

An example of a built-in API that uses Result:

use std::fs::File;

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

  let f = match f {
    Ok(file) => file,
    Err(error) => panic!("Problem opening the file: {:?}", error),
  };
}

In this case we're pacic!ing when error occurs.

# Methods on Result

Result has some helper methods:

  • unwrap - returns value inside of Ok, or panic!s if there's error. It's a shortcut which can be used instead of match.
  • expect - like unwrap, but allows to specify error message for the potential panic.
  • unwrap_or_else - returns value inside of Ok or executes a lambda passed to it in the case of Error.

# The ? Operator

The ? placed after a Result value works as follows:

  • if it's Ok(value), the value gets returned
  • if there's an error, the containing function returns that error

WARNING

? can only be used in functions that return Result or Option, or any type that implements Try.

? can convert the error to the expected Error type that a function normally would return (theFrom trait needs to be implemented).

Example:

fn read_username_from_file() -> Result<String, io::Error> {
  let mut f = File::open("hello.txt")?;
  let mut s = String::new();
  f.read_to_string(&mut s)?;
  Ok(s)
}

TIP

It could be even shorter with chaining calls:

fn read_username_from_file() -> Result<String, io::Error> {
  let mut s = String::new();
  File::open("hello.txt")?.read_to_string(&mut s)?;
  Ok(s)
}

or even:

fn read_username_from_file() -> Result<String, io::Error> {
  fs::read_to_string("hello.txt")
}

In the case above, ? does not need to convert the error since all the expected errors would be of type io:Error - the same type that the function returns.

Last Updated: 3/12/2023, 5:06:29 PM