Skip to main content

Panic

panic! is a macro that immediately stops normal program execution when something goes seriously wrong. It’s used for unrecoverable errors—situations where the program cannot safely continue.

panic!("Something went terribly wrong!");

When this runs, Rust:

  1. Prints an error message.
  2. Shows a stack trace (if enabled).
  3. Terminates the program (or unwinds the stack).

When should you use panic!?

Use panic! when:

  • A bug has occurred.
  • An assumption in your code is violated.
  • Continuing would cause incorrect behavior or data corruption.

Do not use panic! for expected errors like invalid user input or file-not-found—those should use Result.

How Rust handles a panic​

Rust can handle panics in two ways (controlled by build settings):

  1. Unwinding (default)
    • Rust walks back through the stack and runs destructors.
  2. Aborting
    • Program stops immediately (faster, no cleanup).

Example 1: Simple panic!​

fn main() {
panic!("Crash and burn!");
}

Output (simplified):

thread 'main' panicked at 'Crash and burn!', src/main.rs:2:5

The program stops immediately.

Example 2: Panic from an invalid operation​

fn main() {
let v = vec![1, 2, 3];
println!("{}", v[99]); // Out of bounds → panic!
}

Why this panics:

  • Rust checks array bounds at runtime.
  • Accessing index 99 is invalid → Rust calls panic! internally.

Example 3: Using unwrap() (which may panic)​

fn main() {
let number: Option<i32> = None;
let value = number.unwrap(); // panic!
println!("{}", value);
}
  • unwrap() extracts the value if it exists.
  • If the value is None, it calls panic!.
  • This is convenient for quick prototypes but risky in production code.

Example 4: Custom panic with context​

fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Cannot divide by zero!");
}
a / b
}

fn main() {
let result = divide(10, 0);
println!("{}", result);
}
  • Division by zero is an unrecoverable logic error here.
  • We explicitly panic with a helpful message.

panic! vs Result​

Use panic! when...Use Result when...
Bug in the codeUser input error
Impossible stateFile not found
Program cannot continue safelyNetwork failure

Example using Result instead of panic!:​

fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}

fn main() {
match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}

This allows the program to continue safely.

Catching a panic (advanced)​

You can catch panics using std::panic::catch_unwind, but this is usually for:

  • Testing
  • FFI boundaries
  • Highly robust systems
use std::panic;

fn main() {
let result = panic::catch_unwind(|| {
panic!("This will be caught!");
});

match result {
Ok(_) => println!("No panic."),
Err(_) => println!("Panic was caught!"),
}
}

Summary​

  • panic! is for unrecoverable errors.
  • It stops execution immediately.
  • Common sources: unwrap(), array out-of-bounds, explicit panic!.
  • Prefer Result for recoverable errors.
  • Use panic! to signal bugs, not user mistakes.