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?