selfaware soup

Esther Weidauer

Panic! At the Result<T, E>

On letting Rust programs die when they have to

2022-07-25

I’ve been getting back into Rust again lately and I think I overcame a minor but annoying hurdle in understanding what kinds of usage the compiler actually encourages.

In previuous attempts at using Rust I always assumed that a Result<T, E> always needs to be handled with a match to cover both the Ok<T> branch as well as the Err<E> one. This led to pretty cumbersome code and the alternative was to use .unwrap() or .expect(msg: &str) but based on my previous assumption this felt like a “dirty” way of doing it because if the operation that yielded the Result<T, E> actually failed, the program would panic and terminate.

But here’s the thing, when handling this with a match in many cases I would do something like this:

fn read_notes() -> String {
  let res = fs::read_to_string("notes.txt");

  match (res) {
    Ok(s) => s,
    Err(e) => {
      println!("Error reading notes: {}", e);
      std::process::exit(1);
    }
  }
}
$ cargo run
   Compiling panic v0.1.0 (/Users/esther/Desktop/temp/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/panic`
Error reading notes: No such file or directory (os error 2)

Now that’s not really that different from letting the program panic:

fn read_notes() -> String {
  fs::read_to_string("notes.txt").expect("Error reading notes")
}
$ cargo run
   Compiling panic v0.1.0 (/Users/esther/Desktop/temp/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/panic`
thread 'main' panicked at 'Error reading notes: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The main drawback here is less control over how the error is printed out but that is very often not relevant for tools I write for myself. The important information is still there. But it is much shorter and easier to read and understand, especially when the program does a few things in sequence that all yield a Result<T, E>.

I actually want my programs to terminate on errors that I havent explicitly coded a recovery path for. This is why I also always do set -e in Bash scripts. Even something that runs as a server or daemon can and should do this so the error is properly logged and the program can be restarted by something like launchd or systemd.

So, I’ll embrace the panic some more, where appropriate, and have a much easier time making progress with Rust.

🦀🦀🦀🦀