15.2 Unrecoverable Errors in Rust

When Rust encounters an unrecoverable error, it triggers a panic, terminating the current thread and unwinding the call stack (unless configured to abort immediately). Typical triggers for a panic include:

  • Indexing beyond array or vector bounds
  • Integer overflow in debug mode
  • Division by zero
  • Invalid UTF-8 conversions
  • Calling unwrap() or expect() on a None or Err

15.2.1 The panic! Macro and Implicit Panics

You can call panic! directly to halt execution, print an error message, and produce a backtrace:

fn main() {
    panic!("A critical unrecoverable error occurred!");
}

Some operations trigger panics implicitly. For instance, attempting to access an array out of bounds panics automatically:

fn main() {
    let arr = [10, 20, 30];
    println!("Out of bounds element: {}", arr[99]); // Panics
}
  • assert! panics if a specified condition is false.
  • assert_eq! / assert_ne! compare two values (for equality/inequality) and panic if the assertion fails.

These macros are especially useful in testing or to verify assumptions during development.

15.2.2 Catching Panics

In Rust, catching a panic is not a common strategy, but it is possible with std::panic::catch_unwind:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        let array = [1, 2, 3];
        println!("{}", array[99]); // This will panic
    });
    
    match result {
        Ok(_) => println!("Code executed without panic."),
        Err(e) => println!("Caught a panic: {:?}", e),
    }
}

Key points about catching panics:

  • Limited Use Cases: Generally only used in tests or FFI (Foreign Function Interface) boundaries.
  • Not for Control Flow: Panics indicate unrecoverable errors, not ordinary branching logic.
  • Performance Overhead: Stack unwinding and panic catching impose a cost.

15.2.3 Customizing Panic Behavior

You can configure panic behavior through your Cargo.toml or environment variables:

  • Panic Strategy: In Cargo.toml:

    [profile.release]
    panic = "abort"
    
    • unwind (default): Rust unwinds the call stack, calling destructors (drop) along the way.
    • abort: The process terminates immediately without unwinding.
  • Backtraces: By setting RUST_BACKTRACE=1, you can get a detailed stack trace during a panic:

    RUST_BACKTRACE=1 cargo run
    

15.2.4 Stack Unwinding vs. Aborting

  • Stack Unwinding (unwind)
    Calls destructors for all local variables, ensuring resources are released. This makes debugging easier but increases binary size.

  • Immediate Termination (abort)
    Terminates the process right away without running destructors. This can reduce binary size and potentially speed up panic scenarios, but it may leave resources unclosed and make debugging more challenging.

By default, Rust uses stack unwinding for a safer shutdown, but you can switch to abort mode when performance or binary size is critical (e.g., in embedded contexts).