100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Rust

panic! and Unwinding in Rust

Learn how panic! signals unrecoverable errors in Rust, how stack unwinding works, and when to use it versus Result.

Error HandlingIntermediate8 min readJul 8, 2026
Analogies

Introduction

While Result models recoverable errors, panic! signals an unrecoverable error — a bug or violated invariant that the program cannot meaningfully continue past. Calling panic!("message") prints an error message and, by default, unwinds the stack, running destructors along the way before terminating the thread.

🏏

Cricket analogy: A rain-soaked, unplayable pitch that forces the umpires to abandon the match entirely is like panic! — an unrecoverable state — while a dropped catch that just costs runs is handled and play continues, like a Result::Err.

Syntax

rust
fn check_invariant(value: i32) {
    if value < 0 {
        panic!("value must be non-negative, got {}", value);
    }
}

Explanation

By default, panic! triggers stack unwinding: Rust walks back up the call stack, running the destructors (Drop implementations) of every value in scope, cleaning up memory as it goes. In rare cases, unwinding can be intercepted with std::panic::catch_unwind, though this is not meant to replace normal error handling. Alternatively, Cargo.toml can be configured with panic = "abort" under a [profile] section, which causes the process to terminate immediately on panic without unwinding — producing a smaller binary but skipping cleanup. The key idiom: use panic! for programming errors and violated invariants (bugs), and use Result for expected, recoverable failure conditions such as invalid user input or a missing file.

🏏

Cricket analogy: When a match is abandoned, ground staff still methodically pack up the covers, stumps, and boundary ropes in order (Drop running during unwind); a match referee's post-mortem review is like catch_unwind, while a decision to just cancel the tour outright with no cleanup mirrors panic=abort — smaller overhead, but nothing gets tidied.

Example

rust
struct Guard;

impl Drop for Guard {
    fn drop(&mut self) {
        println!("Guard dropped during unwind");
    }
}

fn risky_operation(fail: bool) {
    let _g = Guard; // will be dropped even if we panic below
    if fail {
        panic!("something went seriously wrong");
    }
    println!("operation succeeded");
}

fn main() {
    let result = std::panic::catch_unwind(|| {
        risky_operation(true);
    });

    match result {
        Ok(_) => println!("no panic occurred"),
        Err(_) => println!("caught a panic"),
    }
}

Output

rust
thread 'main' panicked at 'something went seriously wrong', src/main.rs:11:9
Guard dropped during unwind
caught a panic

Key Takeaways

  • panic! is for unrecoverable errors and violated invariants, not for expected/recoverable failures — use Result for those.
  • By default, a panic unwinds the stack, running Drop implementations for cleanup before the thread terminates.
  • std::panic::catch_unwind can intercept a panic, but it is rarely appropriate outside frameworks and FFI boundaries.
  • Setting panic = "abort" in Cargo.toml skips unwinding for a smaller binary and immediate process termination.
  • .unwrap() and .expect() internally trigger panic! when called on Err or None.

Practice what you learned

Was this page helpful?

Topics covered

#Rust#RustProgrammingStudyNotes#Programming#PanicAndUnwindingInRust#Panic#Unwinding#Syntax#Explanation#StudyNotes#SkillVeris