Great analysis of unwinding overhead in Rust. The framing of exceptions as "alternate returns" is enlightening - they <i>should</i> be cheap in theory, which makes the current ~2.3μs overhead particularly interesting to dissect. The optimization journey from removing unnecessary type erasure to using thread-locals is well explained. While the 4.3x speedup is impressive, I think the bigger takeaway isn't about replacing Result with panics, but rather identifying where non-local control flow genuinely makes sense. Looking forward to the deep dive into unwinder implementations.
Well, unwinding can be as simple (and as fast) as<p><pre><code> MOV SP, [installed_handler]
JMP [installed_handler+WORD]
</code></pre>
but it only works if you don't need to run the defers/Drop's/destructors/etc. for stuff that's on the stack between the current frame and the handler's frame. Which you do, most of the time.
This isn't benchmarked against returning a Result right?<p>Like wouldn't bypassing any unwinding be faster than improving unwinding. You already seem to have control over the code as the thrown and caught exception have to be the same so might as well just write the code as a `Result<T, Exception>` in the first place no?
> Returning to an alternate address shouldn’t be significantly more expensive than returning to the default address, so this has to be cheap.<p>Modern CPUs add complications to arguments like this. Branches stall the execution pipeline, so branch prediction was invented to keep the pipeline flowing. Return instructions are <i>perfectly</i> predicted, which makes them literally free. At the very least, any alternate return scheme has to pay for a full misprediction. That can be expensive.
The most interesting part of this article for me is at the beginning.<p>> Now imagine that calls could specify alternate return points, letting the callee decide the statement to return to:<p><pre><code> // Dreamed-up syntax
fn f() {
g() alternate |x| {
dbg!(x); // x = 123
};
}
fn g() -> () alternate i32 {
return_alternate 123;
}
</code></pre>
This sort of nonlocal control flow immediately calls to mind an implementation in terms of continuation passing style, where the return point is given as a function which is tail called. Nonlocal returns and multiple returns are easy to implement in this style.<p>Does there exist a language where some function<p><pre><code> fn foo() -> T throws U
</code></pre>
is syntactic sugar for something more like?<p><pre><code> fn foo(ifOk: fn(T) -> !, ifExcept: fn(U) -> !) -> !</code></pre>