One way to jump accross function boundaries is using std::panic::resume_unwind to trigger panicking without invoking the global panic hook and std::panic::catch_unwind to catch the panic and inspect its payload, similar to catching exceptions.
If you want to return early upon inspecting an Err, this obviously isn't the solution.
However, there may be extreme situations where jumping accross function boundaries may be desirable:
- You want your closure to short-circuit code from a third-party library not supporting short circuting via closures returning
Option/Result/ControlFlow. - You want your closure to report errors back but the third-party library does not provide a method for calling code to functionally obtain those errors.
Before using panics as control flow, consider other solutions:
- Rewriting APIs to accept short-circuiting
- Use &mut T or interior mutability to record errors
- Find alternatives.
This solution is not recommend for having substantial disadvantages:
- Unwinding is not always available e.g. if code is compiled with panic = "abort". If your code can be compile without unwinding, you will have to provide another solution anyway.
std::panic::catch_unwind is optimized for the no-panicking path. If not short-circuiting is not a performance issue, then adding std::panic::catch_unwind and std::panic::resume_unwind may actually worsen performance. - The panic must occur within the
std::panic::catch_unwind call. It can be ambiguous when the closure will actually run due to lazy evaluatio and if you can choose when it will. If the panic occurs outside of the std::panic::catch_unwind call, then all this machinery is pointless. - This solution may look complicated and unidiomatic.
If you have considered all possible solutions and decided that std::panic::catch_unwind may be a solution, below is an example of the pattern:
// Only valid if unwinding is enabled #[cfg(panic = "unwind")] fn f() -> i32 { let some_result = Err(()); // Struct to mark the break signal specific to this code struct Break; // Catch all panics match std::panic::catch_unwind(|| some_result.unwrap_or_else(|_| { // Trigger panicking with the break signal as payload without invoking the global panic hook. std::panic::resume_unwind(Box::new(Break)) }) ) { Ok(i) => i, // Inspect the payload if panic was thrown. // If you are certain nothing inside std::panic::catch_unwind other than your signal can panic, // you can use panic!() or std::panic::resume_unwind(Box::new(())) instead, // skip checking the type of the payload and just return the default value. Err(e) => // If its the specified signal, return a default value if e.is::<Break>() { 1 } // Else resume panicking with that payload else { resume_unwind(e) }, } }
.unwrap_or(1)