kota's memex

Rust doesn't have exceptions! Instead, it has the type Result<T, E> for recoverable errors and the panic! macro that stops execution when the program encounters an unrecoverable error.

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),
    }
}

result

The Result type is an enum which has the variants Ok and Err.

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

unwrap

The unwrap_or_else method is useful for handling an error returned from a function call:

let config = Config::build(&args).unwrap_or_else(|err| {
    println!("Problem parsing arguments: {err}");
    process::exit(1);
});

err or nothing

Sometimes a function will return an error or nothing (empty tuple) so we don't want to call unwrap. In this case we can use if let:

if let Err(e) = run(config) {
    println!("Application error: {e}");
    process::exit(1);
}

is_ok

A helper method exists for quickly checking if there was no error:

let ignore_case = env::var("IGNORE_CASE").is_ok();

propogate

A lot of the time you'll want to send the error up to the caller:

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),
    }
}

This pattern of propogating the error is so common that rust provides a helper in the form of the ? operator:

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)?;
    return Ok(username);
}

The ? placed after a Result value is defined to work in almost the same way as the match expressions in the first example. If the value of the Result is an Ok, the value inside the Ok will get returned from this expression, and the program will continue. If the value is an Err, the Err will be returned from the while function as if we had used the return keyword.

There is one small difference though. The ? operator calls the from function on any error values it processes to convert them to the error return type we've given in our function signature. This is useful when a function returns one error type to represent all the ways a function might fail, even if parts might fail for many different reasons.

Rust programmers LOVE to chain shit together. So this is quite common:

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

We’re only allowed to use the ? operator in a function that returns Result, Option, or another type that implements FromResidual.

main error return

You can actually change the return type of main slightly and Result<(), E> is in fact valid. This means you can use it with the ? operator.

panic

Sometimes you need a mechanism to indicate an irrecoverable programming mistake (rather than an input error). For this usecase rust has panic!() which unwinds the stack, cleaning up all data along the way. This can be configured to instead exit immediately (relying on the operating system's cleanup mechanisms).

Remember: a panic is always better than undefined behavior!

expect

For the case of return the okay value or panic on the error you can use the expect() method:

let greeting_file = File::open("hello.txt")
    .expect("hello.txt should be included in this project");