How to create a Future from a Result containing a Future?

Viewed 349

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.

2 Answers

Probably what one usually wants is to deal with the Err case immediately rather than waiting until the future is .awaited, 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) })
  }
}
Related