Automatic "?" inside try {...} blocks

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 fns implicitly propagate the errors of other try fns 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 ?Results as well as on Results.

This is far more important for Futures, 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.

1 Like