mallwright
mallwright

Reputation: 1975

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

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.

Upvotes: 8

Views: 2839

Answers (2)

trent
trent

Reputation: 28075

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).

Upvotes: 3

Ibraheem Ahmed
Ibraheem Ahmed

Reputation: 13618

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) })
  }
}

Upvotes: 1

Related Questions