Unrecoverable Errors in Rust: Using panic! Safely

Rust treats most errors as recoverable using the Result type. But for serious, unrecoverable failures like bugs in logic or unsafe conditions, you use the panic! macro. It immediately stops the program and prints a message.

Rust is designed to help you write safe, reliable code. Most of the time, it encourages handling errors through Result. But sometimes, you want the program to crash immediately when something unexpected happens. That is what panic! is for.

In this post, I will explain when to use panic, how it works, and why it should be used only in rare situations.

If you have not read Error Handling with Result, you should read that first. It covers how to handle common failures safely.

What Is panic!?

panic! is a macro that causes the program to terminate immediately with an error message.

Example

fn main() {
    panic!("Something went terribly wrong!");
}

This prints the message and a stack trace, then exits.

When to Panic

You should panic when:

  • You reach a logic bug that should never happen
  • Continuing would lead to corrupted data
  • There is no reasonable way to recover

Examples include:

  • Indexing beyond the bounds of an array
  • Unwrapping a None from an Option that should never be None
  • Failing assertions in tests

Rust encourages failing early and loudly if things go wrong unexpectedly.

Panicking on Indexing

Rust panics if you index out of range:

let nums = vec![1, 2, 3];
let value = nums[10]; // will panic

Use get() instead to avoid this:

match nums.get(10) {
    Some(v) => println!("{}", v),
    None => println!("Index out of bounds"),
}

panic! in Functions

You can also panic inside your own functions:

fn get_first_char(s: &str) -> char {
    s.chars().next().expect("String was empty")
}

If the string is empty, expect() calls panic with a message.

panic vs Result

Use Result when the error is expected and recoverable, like:

  • File not found
  • User input is invalid
  • Network failure

Use panic! for bugs in your code, like:

  • A required invariant is broken
  • A value should never be None or zero, but is

panic in Tests

Tests use panic to fail assertions:

#[test]
fn math_test() {
    assert_eq!(2 + 2, 4);
}

If the test fails, assert_eq! triggers panic.

You can also write custom panic cases:

#[test]
#[should_panic]
fn this_should_fail() {
    panic!("Expected failure");
}

Customizing panic behavior

By default, panic prints a message and backtrace. You can control it by setting the RUST_BACKTRACE environment variable:

RUST_BACKTRACE=1 cargo run

This gives you a full trace of the error.

Summary Table

Use panic when…Use Result when…
A bug breaks your program’s logicA failure is expected and can be handled
You want to crash fast in unsafe conditionsYou want to return and recover gracefully
A test should fail if something goes wrongYou want to continue or retry an operation

Final Word

Use panic! only when your program is in a state where continuing is unsafe or meaningless. Rust’s safety model helps you avoid panics in most real applications, but it is still a useful tool for catching serious bugs early.