15.3 The Result Type

While some errors demand termination, many can be anticipated and handled gracefully at runtime. Rust’s standard approach to these scenarios is the Result type, which enforces explicit handling of both successes and failures—much like returning an error code in C, but with compile-time checks that make ignoring an error more difficult.

15.3.1 Understanding the Result Enum

Result is an enum defined as:

enum Result<T, E> {
    Ok(T),
    Err(E),
}
  • Ok(T) holds a success value of type T.
  • Err(E) holds an error value of type E.

15.3.2 Comparing Option and Result

Rust also provides the Option<T> enum:

enum Option<T> {
    Some(T),
    None,
}
  • Option<T> expresses the possibility of an absence of a value (not necessarily an error).
  • Result<T, E> expresses an operation that might fail and produce error information.

15.3.3 Basic Use of the Result Type

Here’s a simple example that parses two strings into integers and multiplies them:

use std::num::ParseIntError;

fn multiply(first_str: &str, second_str: &str) -> Result<i32, ParseIntError> {
    match first_str.parse::<i32>() {
        Ok(first_number) => match second_str.parse::<i32>() {
            Ok(second_number) => Ok(first_number * second_number),
            Err(e) => Err(e),
        },
        Err(e) => Err(e),
    }
}

fn main() {
    println!("{:?}", multiply("10", "2")); // Ok(20)
    println!("{:?}", multiply("x", "y"));  // Err(ParseIntError(...))
}

To reduce nesting, you can use and_then and map:

use std::num::ParseIntError;

fn multiply(first_str: &str, second_str: &str) -> Result<i32, ParseIntError> {
    first_str.parse::<i32>().and_then(|first_number| {
        second_str.parse::<i32>().map(|second_number| first_number * second_number)
    })
}

fn main() {
    println!("{:?}", multiply("10", "2")); // Ok(20)
    println!("{:?}", multiply("x", "y"));  // Err(ParseIntError(...))
}

15.3.4 Using Result in main()

While main() normally returns (), you can also have it return a Result:

use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
    let number_str = "10";
    let number = number_str.parse::<i32>()?;
    println!("{}", number);
    Ok(())
}

An error will cause the program to exit with a non-zero code, whereas returning Ok(()) exits with code 0.