Error Handling

Error handling is an essential aspect of any programming language, and Rust is no exception. Rust provides robust error handling mechanisms, like the Result type and the ? operator, which allow you to deal with errors in a clean and idiomatic way. And you can also bail out for unrecoverable errors with panics.

The Result Type

Rust has a built-in Result enum for handling errors in a type-safe manner. The Result type is defined as follows:

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

The Result enum has two variants:

  • Ok, which represents a successful operation and contains the result value
  • Err, which represents a failed operation and contains the error value

The Result type encourages you to handle errors explicitly and provides a clear separation between the success and error cases. This is one of the things that makes Rust so powerful for writing robust code: You can't forget to handle errors, the type system will require that you address them.

You can use pattern matching to handle the different variants of the Result type:

fn main() {
    let result = some_function();

    match result {
        Ok(value) => println!("Success: {}", value),
        Err(error) => println!("Error: {}", error),
    }
}

fn some_function() -> Result<String, String> {
    // ...
}

You can also use unwrap or expect if you know the returned value is an Ok. But you have to be very sure: if you unwrap an Err, your program will panic! This is okay in instances like missing configuration where you don't want the program to run at all if it isn't there, but you have to be deliberate. As a general rule, don't use unwrap or expect unless you want the program to crash if it fails.

The ? Operator

While pattern matching on Result values is powerful, it can become verbose when dealing with multiple operations that may return errors. To simplify error handling in these situations, Rust provides the ? operator. It's only usable when you're expecting to return a Result value, and in those cases is tremendously helpful.

The ? operator, when placed after an expression that returns a Result, automatically handles the error case. If the expression returns an Err, the function immediately returns the error value. If the expression returns an Ok, the ? operator unwraps the Ok value and continues executing the function.

Here's an example of using the ? operator:

fn main() {
    match read_config() {
        Ok(config) => println!("Config: {:?}", config),
        Err(error) => println!("Error: {}", error),
    }
}

fn read_config() -> Result<String, String> {
    let file = read_file("config.txt")?;
    let parsed = parse_config(&file)?;
    Ok(parsed)
}

fn read_file(path: &str) -> Result<String, String> {
    // ...
}

fn parse_config(file_contents: &str) -> Result<String, String> {
    // ...
}

In this example, the read_config function calls two other functions that return Result values. Instead of using pattern matching to handle the errors, the ? operator is used to simplify the code. It elevates the "happy path" so that you can read through that directly, and you know that errors will trickle up.

Panics

Rust has another mechanism for error handling called panics. A panic is a runtime error that results in the immediate termination of the program. Panics are reserved for exceptional situations where it's impossible or undesirable to continue executing the program, such as an unrecoverable error or a broken invariant.

You can cause a panic explicitly using the panic! macro:

fn main() {
    let index = 10;
    if index > 5 {
        panic!("Index is out of bounds!");
    }

    // ...
}

When a panic occurs, Rust unwinds the stack, running destructors for all objects in scope and then terminating the program. Alternatively, Rust can be configured to abort the program directly, without unwinding the stack.

Panics should be used sparingly and only in exceptional circumstances. In most cases, you should prefer using the Result type for error handling, as it promotes explicit and robust error handling.