Exceptions don't do that, they stop the flow entirely, then match it to a point arbitrarily higher on the stack, and resume after that whole sub-tree has been destructed.
They're also much more efficient than branching and maybe returning on the result of every single function call.
Exceptions actually do that, except hidden and unsignaled.
> They're also much more efficient than branching and maybe returning on the result of every single function call.
Not when actually taken.
Which IMO is good in exactly one situation: when raising the exception means that the program contains a bug.
Using panics (ah sorry... exceptions) in this case is justified as it should be really exceptional (if there is a bug anyway we have more pressing problems than performance) and in the absence of bug if we were to use a Result type it would mean we would have a "BugError" variant that is actually dead code everywhere where the program is not buggy.
So in my opinion a correct approach is to unwrap whenever you have an invariant that guarantees that there should be a value, with a panic handler set at the boundary of the logical task to fail the entire logical task in case there is a bug. A logical task can be an asynchronous light task, a thread, or the whole process depending on the situation.
I much prefer it not being the whole process when the process is e.g. a web server or a word processor (and the failure occurred somewhere in an ancillary function)
(I don't see why it should, either. The blanket "efficiency" argument is unconvincing to me).
Ok, I see one reason: The programmer might want control which implementation is used. That would require an additional mini-feature in the language syntax/function types. But this still wouldn't be an argument for a whole different syntax and forced separate code paths as required for traditional exceptions. And it's theoretic anyway -- I don't think it's important to give the user this "control".
The advantage of ADT approach is that you can store error unions more permanently when it makes sense. It is not additional syntax, unlike exceptions. In that sense it is the simpler approach of the two. If anything it is exceptions that has a little bit more of an explosion of complexity because you have to specify stuff using multiple mechanisms (types vs exceptions), and possibly have to switch between the two when refactoring.
I say that as someone who doesn't think highly of either approach. In my view, plain error values are fine, there isn't any clever language solution needed. If you find yourself checking return values a lot (as opposed to storing error values in handles and checking them at strategic locastions), that can hint an architectural problem.