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.