← Back to Rust series
πŸ¦€
Error handling
Error handling Β· Prerequisite: lesson 16

17. panic! and Unrecoverable Errors

panic is Rust's way of saying "this is unrecoverable, stop now." Array bounds, integer division by zero, and `.unwrap()` on None all trigger panic automatically. This lesson covers how panic works, unwind vs. abort, and when to invoke panic on purpose.

Rustpanicunwrapasserterror handling
Duration
⏱ ~1 hour
Level
πŸ“Š Intermediate
Prerequisite
🎯 Lesson 16
OUTCOME
panic is Rust's way of saying "this is unrecoverable, stop now." Array bounds, integer division by zero, and `.unwrap()` on None all trigger panic automatically. This lesson covers how panic works, unwind vs. abort, and when to invoke panic on purpose.

What you'll learn

  • 1List situations that automatically panic
  • 2Trigger an explicit panic with the `panic!` macro
  • 3Distinguish unwind vs. abort
  • 4Use assert! / assert_eq! / debug_assert!
  • 5Choose between Result-handling and panic

Overview

Panic resembles an unchecked exception in other languages, but Rust's messages are clean and stack traces are tidy. Most panics are **safety nets that surface bugs immediately** β€” debug-build integer overflow, .unwrap() on None, etc.

Core Concepts

1) Situations that auto-panic

  • Array / slice index out of bounds
  • Integer overflow (debug builds)
  • Integer division by zero
  • Option::unwrap() on None, Result::unwrap() on Err
  • RefCell borrow-rule violations at runtime

2) Explicit panic

rust
panic!("intentional stop β€” TODO: implement");
assert!(n > 0, "n must be positive");
assert_eq!(actual, expected, "values differ");

3) unwind vs. abort

StrategyBehaviorDefault
unwindRuns Drops, releases resourcesDev/release default
abortExit immediately, no DropOpt-in via Cargo.toml panic = "abort" (smaller binaries)

4) When to panic vs. return Result

  • **panic** β€” unrecoverable bugs / invariant violations (continuing would be worse)
  • **Result** β€” user input / external I/O / things that legitimately fail

Hands-on Examples

rust
fn divide(a: i32, b: i32) -> i32 {
    assert!(b != 0, "cannot divide by zero");
    a / b
}

fn main() {
    println!("{}", divide(10, 2)); // 5
    println!("{}", divide(10, 0)); // thread 'main' panicked at 'cannot divide by zero'
}

Debug-only assertion (stripped in release):

rust
fn process(v: &[i32]) {
    debug_assert!(!v.is_empty(), "empty slice passed");
    // ... real processing
}

Switch panic strategy via Cargo.toml:

toml
[profile.release]
panic = "abort"     # smaller binaries, no Drop

See stack traces:

bash
RUST_BACKTRACE=1 cargo run
# Detailed call stack up to the panic site

Common Mistakes

Q. Is .unwrap() also a panic?

A. Yes β€” None/Err triggers panic. For user input or I/O prefer `?` or `.unwrap_or()`.

Q. Can I catch panics?

A. `std::panic::catch_unwind` exists but is not a general error-handling tool β€” it's for FFI boundaries or thread isolation. Day to day, use Result.

Q. debug_assert! gets stripped in release β€” can I rely on it?

A. It's a dev-time check. **For checks that must remain in release, use `assert!`** (always active).

Recap

  • panic = unrecoverable, immediate stop
  • Bounds / overflow / .unwrap on None trigger it automatically
  • Explicit: panic! / assert! / assert_eq! macros
  • panic for bugs / invariant violations, Result for legitimate failures
  • RUST_BACKTRACE=1 for stack traces

Try It Yourself

  1. Intentionally trigger an out-of-bounds index and read the panic message + stack trace
  2. Write a simple test function with assert_eq! and verify it panics on a wrong value
  3. Switch release profile to abort and compare binary size
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—