🎓 A1Learner
🔍

Rust Catching Panics

catch_unwind is the tool that catches a panic before it ends the program. catch_unwind stops that unwinding at one border and returns the panic as a value.

catch_unwind catches the panic

Run risky code inside catch_unwind. A panic inside comes back as a value you can test.

let caught = panic::catch_unwind(|| panic!("boom"));
println!("panicked: {}", caught.is_err());

No panic means Ok

When the closure finishes calmly, catch_unwind hands back Ok with its value.

let ok = panic::catch_unwind(|| 2 + 2);
println!("{ok:?}");

Read the message out of the payload

The Err holds whatever panic! was given. downcast_ref reads a text message back out.

let bad = panic::catch_unwind(|| panic!("boom"));
if let Err(e) = bad {
    println!("caught: {}", e.downcast_ref::<&str>().unwrap());
}

Mutable captures need AssertUnwindSafe

A closure that changes a captured value needs a promise that the value is still usable after a panic.

let mut log = vec![1];
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| { log.push(2); panic!("stop"); }));
println!("{log:?}");

Catch, clean up, then hand the panic back

resume_unwind restarts the same panic with the same payload — used by test runners and frameworks that must not swallow failures.

let result = panic::catch_unwind(|| -> i32 {
    panic!("step two exploded");
});
match result {
    Ok(v) => println!("value = {v}"),
    Err(payload) => {
        println!("cleanup finished; now hand the panic back");
        panic::resume_unwind(payload);
    }
}

Example

use std::panic;
fn main() {
    println!("start");
    let ok = panic::catch_unwind(|| 2 + 2);
    println!("calm call: {:?}", ok);
    let bad = panic::catch_unwind(|| panic!("boom"));
    match bad {
        Ok(_) => println!("no panic"),
        Err(e) => {
            if let Some(msg) = e.downcast_ref::<&str>() {
                println!("caught a panic: {}", msg);
            }
        }
    }
    println!("still running");
}

Key points

  • The panic message still prints to stderr as usual — catching stops the unwinding, not the report
  • Catching only works with the default unwind panic strategy.
  • With panic = "abort" the program ends at once.
  • Nothing can be caught.
  • Use catch_unwind rarely: only at a border.
  • A thread, a request, a plug-in or foreign-language code can form that border.
  • Never use it as everyday error handling.
  • The stack normally unwinds when a panic happens.

In this example, main first runs a calm closure, || 2 + 2, inside catch_unwind. Nothing panics, so the result is Ok(4) and the program prints it. Then a second closure panics with "boom". The panic unwinds only as far as catch_unwind, which hands back Err with the payload inside. downcast_ref::<&str>() reads the message out of the payload. So main prints caught a panic: boom. And then main is still running. The program survived. You will also see the usual panic report on stderr. Catching does not hide it.

Note

Rust has no try/catch in ordinary code on purpose.

Result is the normal, visible path for failure.

catch_unwind exists for the few borders where a crash must not spread.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does panic::catch_unwind give back when the closure panics?