In this lesson, you will learn how Rust safely shuts down execution when it encounters an unrecoverable error and how you can prevent a crash from taking down your entire program.
What is a Panic?
Sometimes, a program reaches a state where it is impossible to continue—like trying to access an index outside of an array or calling panic! explicitly. When this happens, Rust initiates a panic. By default, Rust performs stack unwinding.
Think of unwinding like a "clean-up crew" moving backward through your code. As the panic travels up the stack, Rust systematically cleans up all the local variables and objects that were created in each function. It calls the drop method for every object that goes out of scope, ensuring that memory is freed and resources (like file handles or network connections) are closed correctly.
Catching Panics at Thread Boundaries
Because unwinding happens while a thread is effectively "dying," you cannot catch a panic inside the same function where it started. However, Rust allows you to contain a panic at the thread boundary.
If you spawn a new thread using std::thread::spawn, you can capture the result of that thread's execution. If the thread panics, the join() method will return an Err containing the panic information, rather than crashing your main program.
use std::thread;
fn main() {
let handle = thread::spawn(|| {
panic!("Something went wrong in the thread!");
});
// We catch the result here instead of the thread crashing the whole app
match handle.join() {
Ok(_) => println!("Thread finished successfully."),
Err(_) => println!("Caught a panic! The main thread is still safe."),
}
}
By wrapping risky operations in a separate thread, you create a "sandbox." The main thread remains alive, allowing your program to report the failure, log the error, or attempt a retry without shutting down the entire application.
Key Takeaway: A panic triggers a systematic clean-up of the stack (unwinding) to prevent resource leaks, and you can contain these failures by catching them at thread boundaries.