294. AssertUnwindSafe — catch_unwind Won't Touch Your &mut Until You Sign the Waiver
You wrapped the job loop in catch_unwind (bite 291), added a completed-counter — and the build broke: the type `&mut i32` may not be safely transferred across an unwind boundary. AssertUnwindSafe is how you tell the compiler you’ve thought it through.
catch_unwind requires its closure to be UnwindSafe — a marker trait, like Send, that the compiler derives automatically. Capturing a &mut breaks it:
| |
The concern is logical corruption, not memory safety: if the closure panics halfway through updating something it borrowed, you catch the panic and then keep using data that may be half-updated. Types with poisoning like Mutex (bite 290) handle this themselves and stay UnwindSafe; a raw &mut T or RefCell<T> can’t make that promise, so catch_unwind refuses them.
Here the fix is a judgment call, and it’s an easy one — completed += 1 is the last statement, so a panic can’t leave it torn. Wrap the closure in AssertUnwindSafe to vouch for it:
| |
AssertUnwindSafe(x) is a zero-cost wrapper that implements UnwindSafe unconditionally — no unsafe, no runtime check. You can’t cause undefined behavior with it; the worst case is observing state a panic left half-updated, which is exactly what you’re asserting can’t happen (or doesn’t matter).
Rule of thumb: on Err, either discard the state the closure touched or make sure every mutation is panic-proof — then AssertUnwindSafe is a fact, not a wish.