On the other hand, having some context in which errors are propagated implicitly would open the door to an interesting kind of polymorphism- call it “try
-polymorphism.” The very same behavior that @josh is concerned about—a function call starting to propagate an error without the caller making a decision on how to handle it—would be quite useful in functions that take and call closures.
For example, look at the unwrap_or_else
function. Its closure argument must return the same type as the Result
's T
argument. Which means if you want to use ?
, you have to write something like this:
some_result.map(Ok).unwrap_or_else(|err| {
let foo = look_something_up()?;
Ok(transform(foo))
})?
Note the extra .map(Ok)
and the double ?
. This has to be worked through, ad-hoc, for every combinator you might use. If we made try fn
s implicitly propagate the errors of other try fn
s they call, and made those combinators try
-polymorphic based on their closure argument, you might instead write this:
some_result.unwrap_or_else(try |err| {
let foo = look_something_up();
transform(foo)
})
This is also doable without the implicit propagation, but it would make writing try
-polymorphic functions kind of odd- what would be the type of a function call expression that may or may not be try
? We’d have to extend try
-polymorphism from functions to values. And then ?
would have to operate on those ?Result
s as well as on Result
s.
This is far more important for Future
s, where you can’t just .map(future::ok)
because that results in a different Future
type than the async
closure. You would actually have to use a completely separate combinator function that takes async
closures! And a “polymorphic value” of type ?Future<Output=T>
is even weirder than the above ?Result<T>
- the async
-polymorphic combinator’s choice of where to ?await!
would change the order of execution, not just where it early-exits.
I doubt this polymorphism is worth that complexity at the value level, but the complexity at the function level is far less. C++ already has it in the form of noexcept
expressions, which they use to e.g. select a faster algorithm for std::vector
resizing when the element is noexcept
-movable. And I know we’ve all run into the desire to write ?
in a closure before.