(I.e. it's quite different from what was done with "async fn" support, where the Future return type is hidden via the 'async' specifier and the control flow is totally changed.)
Indeed. Because manually handling those are a pain in the butt. So they made a shortcut. That still needs to be handled by someone up in the hierarchy. Exceptions in all but name.
> It's not even monad-like because
No one cares whether it's monads or not.
But with added space and branch-predictor overhead in every call site, and runtime overhead in every call event!
The result of the branch method on Try is ControlFlow<Self::Residual, Self::Output>
Try isn't stable, but ControlFlow is. They've reified the control flow! Rust has a type which represents the idea of a decision whether to return early. This is in my opinion pure genius, and it happened almost by accident. It seemed natural at first that the decision to return early is manifested directly in Result, but it isn't. That's the insight. Failure and returning early are distinct ideas, and we might just as easily want to return early in consequence of success as failure.
In separating "Success versus Failure" from "Return early versus keep going" Rust gets a lot more value here than is encapsulated in C++ Exceptions.
In particular, checked exceptions.
This is a very interesting article that discusses the isomorphism between checked exceptions and error return types, they ended up with checked exceptions where a function Foo that may throw must be invoked with "try Foo()" - very similar to Rust's ADT-based macro/syntax.
- unions are not a panacea and are a pain to handle manually, as you wrote;
- but with a little syntactic sugar, they turn out to work really, really well, much better than C++-style exceptions.
There's something to be said for OCaml-style exceptions, which are actually even closer to zero-cost, but I wouldn't call OCaml a systems programming language.
Writing this as one of the persons who advocated for try! around the time of Rust 0.4 :)
What they have actually shown is that error unions are not a panacea and a pain to handle manually. And that hardcore killing your app in the presence of even the tiniest of unhandled errors isn't suitable for any programming, especially systems programming.
That's why Rust ended up introducing try!, ? and catching panics. Go also added panic recovery.