Is it possible (logical?) to convert a result containing a future into a future that resolves to a result?
The following function is a bit broken, but hopefully makes what I am trying to achieve more clear:
use std::future::Future;
fn transpose<T,U,E>(result: Result<T,E>) -> impl Future<Output = Result<U, E>>
where T: Future<Output = Result<U,E>> /* not sure if this is the correct trait bound */ {
match result {
Ok(inner) => /* a future that eventually resolves to a Result<U, E> */
Err(error) => /* a future that immediately resolves to a Result<U, E>::Err(error) */
}
}
To give some context: I found myself needing to do this after calling a async
function from a closure passed to Result::map
, so perhaps that was my first mistake.
What is a future? A future (lower case “f”) is an instance of the Future (capitalized “F”) class. A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed. Note: Uncompleted is a Dart term referring to the state of a future before it has produced a value.
wait<T> method Null safety Returns a future which will complete once all the provided futures have completed, either with their results, or with an error if any of the provided futures fail.
Futures in rust allow you to define a task, like a network call or computation, to be run asynchronously. You can chain functions onto that result, transform it, handle errors, merge it with other futures, and perform many other computations on it.
A Future is used to represent a potential value, or error, that will be available at some time in the future. Receivers of a Future can register callbacks that handle the value or error once it is available. For example: Future<int> future = getFuture(); future. then((value) => handleValue(value)) .
Probably what one usually wants is to deal with the Err
case immediately rather than waiting until the future is .await
ed, so that might explain why a function like this doesn't already exist. However, if you do need it, it's pretty simple to write using async
and .await
:
fn transpose_flatten<T, U, E>(result: Result<T, E>) -> impl Future<Output = Result<U, E>>
where
T: Future<Output = Result<U, E>>,
{
async { // when polled,
match result {
Ok(good) => good.await, // defer to the inner Future if it exists
Err(bad) => Err(bad), // otherwise return the error immediately
}
}
}
Or the same thing using async fn
(which is not always quite the same thing, but in this case it seems to be):
async fn transpose_flatten<T, U, E>(result: Result<T, E>) -> Result<U, E>
where
T: Future<Output = Result<U, E>>,
{
match result {
Ok(good) => good.await,
Err(bad) => Err(bad),
}
}
Since the Err
value is returned from the enclosing async
block or function, you can use ?
syntax to make it even shorter:
async fn transpose_flatten<T, U, E>(result: Result<T, E>) -> Result<U, E>
where
T: Future<Output = Result<U, E>>,
{
result?.await
}
I called this function transpose_flatten
because to me transpose
sounds like it should take a Result<Future<Output = U>, _>
. This function flattens two layers of Result
(the one passed to the function and the one returned from the future).
If result
is Ok
you can simply return the inner future. However, if it is Err
, then you have to construct a new future resolving to Result<U, E>::Err(e)
. This means that you cannot return a generic Future
because the two match
arms return two distinct types. You have to return a trait object:
fn transpose<T, E: 'static, U: 'static>(result: Result<T, E>) -> Box<dyn Future<Output = Result<U, E>>>
where T: Future<Output = Result<U, E>> + 'static {
match result {
Ok(inner) => Box::new(inner),
Err(error) => Box::new(async { Err(error) })
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With