Hone logo
Hone
Problems

Robust Error Handling: Graceful Panic Recovery in Rust

In Rust, panics are unrecoverable errors that typically lead to program termination. While Rust encourages avoiding panics and using Result for recoverable errors, certain situations might necessitate a controlled response to a panic. This challenge focuses on implementing mechanisms to catch and handle panics gracefully, preventing program crashes and allowing for cleanup or reporting.

Problem Description

Your task is to create a mechanism in Rust that can execute a given piece of code and, if that code panics, catch the panic and execute a predefined cleanup or recovery function. This is often referred to as "panic safety" or "panic unwinding."

Key Requirements:

  1. Execute Arbitrary Code: You must be able to pass a closure or function pointer to your panic-handling mechanism, which will be executed.
  2. Catch Panics: If the executed code triggers a panic, your mechanism should intercept it.
  3. Execute Recovery Logic: Upon catching a panic, a separate, provided closure or function pointer should be executed. This function will contain the "recovery" or "cleanup" logic.
  4. Return Value (Optional): If the executed code does not panic, its successful return value should be propagated. If it does panic, the recovery function's behavior (e.g., returning a default value, an error indicator) should be the outcome.
  5. No Program Termination: The overall program should not terminate due to a panic within the executed code.

Expected Behavior:

  • If the provided code runs to completion without panicking, its return value should be returned by your panic-handling function.
  • If the provided code panics, the panic should be caught, the recovery function should be executed, and the panic-handling function should not panic itself (unless the recovery function explicitly causes a panic).

Edge Cases:

  • Nested Panics: What happens if the recovery function itself panics?
  • Panics in Drop Implementations: Consider how panics in Drop might interact with unwinding. (While fully handling this can be complex, be aware of its existence).
  • Non-'static Closures: How will you handle closures that capture non-'static references?

Examples

Example 1:

// Function to be called
fn might_panic(should_panic: bool) {
    if should_panic {
        panic!("Intentional panic!");
    }
    println!("Execution successful!");
}

// Recovery function
fn handle_panic_recovery(payload: Box<dyn std::any::Any + Send>) -> String {
    println!("Panic caught: {:?}", payload.downcast_ref::<&str>());
    "Recovery successful".to_string()
}

// Input to the panic handler:
// Code: `might_panic(false)`
// Recovery: `handle_panic_recovery`

// Expected Output:
// Execution successful!
// "Execution successful!" (The return value of `might_panic` if it had one, or a default success indicator if not. For simplicity, let's assume a `()` return type that results in `Ok(())`)

Explanation: The might_panic(false) call executes successfully. The panic handler returns the success outcome.

Example 2:

// Function to be called
fn might_panic(should_panic: bool) {
    if should_panic {
        panic!("Intentional panic!");
    }
    println!("Execution successful!");
}

// Recovery function
fn handle_panic_recovery(payload: Box<dyn std::any::Any + Send>) -> String {
    println!("Panic caught: {:?}", payload.downcast_ref::<&str>());
    "Recovery successful".to_string()
}

// Input to the panic handler:
// Code: `might_panic(true)`
// Recovery: `handle_panic_recovery`

// Expected Output:
// Panic caught: Some(&"Intentional panic!")
// "Recovery successful" (The return value of the recovery function)

Explanation: The might_panic(true) call triggers a panic. The panic handler catches it, calls handle_panic_recovery, and returns the value from the recovery function. The program does not terminate.

Example 3: Nested Panic (Demonstrating Robustness)

// Function to be called
fn trigger_panic_then_recover() {
    panic!("Outer panic!");
}

// Recovery function that itself panics
fn recover_and_panic(payload: Box<dyn std::any::Any + Send>) -> String {
    println!("Caught outer panic, now panicking from recovery!");
    panic!("Panic from recovery!"); // This panic might be unrecoverable depending on context
    // unreachable!("Should not reach here")
}

// Input to the panic handler:
// Code: `trigger_panic_then_recover()`
// Recovery: `recover_and_panic`

// Expected Output:
// Caught outer panic, now panicking from recovery!
// (Program would likely terminate here if not handled further up the call stack,
// but the *initial* call to the panic handler should not immediately crash the program,
// and the fact that the recovery function panicked should be observable.)

Explanation: The initial code panics. The recovery function is called and subsequently panics. The goal of your handler is to catch the first panic and attempt to execute recovery. How the second panic is handled is a more advanced consideration, but the core challenge is catching the first one. For this problem, assume the outer panic is caught, and if the recovery panics, that's the eventual outcome.

Constraints

  • Your panic-handling solution must be implemented in Rust.
  • The solution should aim to be as general as possible, allowing for various return types from the executed code.
  • Performance is not the primary concern, but excessive overhead should be avoided.
  • The solution should leverage Rust's standard library features for panic handling.

Notes

Rust's standard library provides std::panic::catch_unwind for this purpose. Familiarize yourself with its signature and usage. Consider how to handle the Result returned by catch_unwind. Think about the payload of the panic and how to extract useful information from it. The Any trait is important for handling arbitrary panic payloads.

Loading editor...
rust