2
votes

I am implementing a wrapper of a C library which takes callbacks and the callbacks will be implemented in Rust. Given that panicking in Rust when calling from C is undefined behavior, I want to catch any potential Rust panics before they get into C.

I have been reading about std::panic::catch_unwind. The wrapper is performance sensitive and I would prefer to avoid using types like Mutex. I would like to hold my result in an Option<i32> and set this to Some(value) in the case where there is no panic. None will indicate that the function did not execute successfully and thus, a panic must have occurred.

Is Option<i32> unwind safe? If not, under what conditions would there be a problem? Can I wrap it in std::panic::AssertUnwindSafe?

Here is an example where I used AssertUnwindSafe to wrap the entire closure.

use std::panic::{self, AssertUnwindSafe};

fn random_function_that_might_panic(a: i32) -> i32 {
    if a == 42 {
        panic!("did you forget a towel?");
    }
    a * 2
}

fn do_not_panic(a: i32) {
    let mut result = None;
    let unwind_state = panic::catch_unwind(AssertUnwindSafe(|| {
        result = Some(random_function_that_might_panic(a)); // get result, but this could panic
    }));
    match unwind_state {
        Ok(()) => {
            match result {
                Some(value) => {
                    println!("Result: {:?}", value);
                }
                None => {
                    // this should never happen...
                    println!("No result but no panic?");
                }
            }
        }
        Err(e) => {
            println!("caught panic: {:?}", e);
        }
    }
}

fn main() {
    do_not_panic(1);
    do_not_panic(2);
    do_not_panic(3);
    do_not_panic(42);
}

(See the above on the playground.)

I could not figure out how to wrap just Option<i32> in AssertUnwindSafe, so here I wrapped the whole closure. How would I wrap just the Option<i32>?

1
It is preferred to post separate questions instead of combining your questions into one. That way, it helps the people answering your question as well as others hunting for one of your questions. Your first 3 questions are fairly intertwined, but "How would I wrap just the Option<i32>?" is distinct. Please ask it separately.Shepmaster
It's not more simple to ask to the compiler to abort instead of panic ?Stargateur

1 Answers

5
votes

Is Option<i32> unwind safe?

Yes. There's no reason to ask a human this question when you can ask the compiler:

fn implements<T: std::panic::UnwindSafe>() {}

fn main() {
    implements::<Option<i32>>();
}

Your real question should be:

Is &mut Option<i32> unwind safe?

This is not, but you probably already knew that. I'm guessing that you got this compiler error before you added AssertUnwindSafe which tells you it isn't safe:

error[E0277]: the trait bound `&mut std::option::Option<i32>: std::panic::UnwindSafe` is not satisfied in `[closure@src/main.rs:12:44: 14:6 result:&mut std::option::Option<i32>, a:&i32]`
  --> src/main.rs:12:24
   |
12 |     let unwind_state = panic::catch_unwind(|| {
   |                        ^^^^^^^^^^^^^^^^^^^ the type &mut std::option::Option<i32> may not be safely transferred across an unwind boundary
   |
   = help: within `[closure@src/main.rs:12:44: 14:6 result:&mut std::option::Option<i32>, a:&i32]`, the trait `std::panic::UnwindSafe` is not implemented for `&mut std::option::Option<i32>`
   = note: required because it appears within the type `[closure@src/main.rs:12:44: 14:6 result:&mut std::option::Option<i32>, a:&i32]`
   = note: required by `std::panic::catch_unwind`

I'd write your code as this, for what it's worth:

fn do_not_panic(a: i32) {
    let result = panic::catch_unwind(|| random_function_that_might_panic(a)).ok();

    match result {
        Some(value) => {
            println!("Result: {:?}", value);
        }
        None => {
            println!("caught panic");
        }
    }
}

No mutable variables, no extra nesting, no "this should never happen" comments.

See also: