Understanding UnwindSafe :)

hi folks! It's getting a bit difficult to wrap my head around this...

Simply put, a type T implements UnwindSafe if it cannot easily allow witnessing a broken invariant through the use of catch_unwind (catching a panic)

Does this mean that if the closure passed to catch_unwind captures T (by exclusive reference for example) and we panic! while the closure was executing and then outside of the closure we observed T in a broken state then Tis !Unwindsafe ?

also, does it make sense to propagate an error to the caller using this std::panic::catch_unwind(|| f())? ?

UnwindSafe doesn't do anything. It doesn't protect against anything real.

It's a purely speculative caution by Rust based on assumptions that panics are unexpected and when something unexpected happens then programs may perhaps - we don't know - leave some data in some inconsistent state. What that state looks like we don't know either. Rust itself is always safe against panics, and the trait doesn't change Rust's behavior. The trait is just "FYI" for applications to guard against some custom application-specific problem with unwinding, if they have one.

Because it's such a vague worry, Rust can't really point to anything specific and will be suspicious of every type that can mutate data that survives a panic.

I think that in practice this whole concept and the entire trait is useless.

It's a leftover from Erlang-inspired proto-Rust that wanted to have separate heaps per thread and Task that dies on panic with all of its data. In that language shared data would actually need special care to be unwind safe (avoid using per-thread GC heaps). But the Rust we have today has nothing like this. The whole idea didn't pan out and was abandoned, except that vestige.

All Rust code is already required to prevent Undefined Behavior when unwinding happens, even when it doesn't implement UnwindSafe. Rust code has deterministic Drop that runs on unwinding which is commonly used to fix or discard any incomplete data on unwinding. So the only reason for UnwindSafe would be having code where somebody cared just enough to make it not-unsafe when panicking, but didn't care enough to make it not buggy and left the bugs in intentionally. But even that doesn't work, because in practice every time a type doesn't implement UnwindSafe, it's because author of the code had no idea the trait existed. So even users who would like to intentionally use absence of UnwindSafe impl to mean something, can't rely on it.

the UnwindSafe bound is placed on the closure type itself. because the UnwindSafe marker is structural, if a closure captures &mut T, the UnwindSafe status of the closure type depends on &mut T, not T. and &mut T is already !UnwindSafe by default. T is UnwindSafe only matters when the closure captures T by value.

yes, but rarely. for instance, it might serve as an adapter between api boundaries.

usually, if you choose to catch the exception, you want to handle it at the point, or at least inspect/transform it in someway before "rethrow" it. otherwise, why bother to catch it instead of letting it propogate naturally?

although the question mark can convert the Err payload through the Into impl, it would be better to make the exception handling explicit, even if you rethrow it afterwards.

To wit, AssertUnwindSafe doesn't require unsafe and allows you to pass any closure to catch_unwind, so UnwindSafe cannot be load bearing.

More conversation here and in the linked PRs/issues.

*triggered*