How to create a Future from a Result containing a Future?
Asked Answered
A

2

8

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.

Archiepiscopal answered 17/12, 2020 at 14:2 Comment(0)
C
3

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

Clarinda answered 17/12, 2020 at 16:10 Comment(5)
I think the OP wanted to convert a result to a future, and .awaiting is actually resolving the future, which is more than a conversion.Jahncke
@Ibraheem I think you're overlooking the async block. The .await does not run when transpose_flatten is called.Clarinda
One thing that I thought would have been possible to avoid is the Err(bad) => Err(bad) line by using the right functional programming method. I guess this isn't the case?Archiepiscopal
@MichaelAllwright In general you can't necessarily avoid it. The obvious idea, result.map(|good| good.await), doesn't work because .await is a control operator that affects the immediately enclosing async function, so you can't wrap it in a closure (much like return). However, since the result of match is returned anyway, you could use ? to propagate the error value: result?.await does the same thing as the match here (but would not if it were not the last expression-statement of the function).Clarinda
I added this variant to the answer. I will note that it doesn't strike me as especially "functional", but I think that's an unfruitful argument. If the code is concise, working and makes sense to you, who cares whether it's "functional programming" or not?Clarinda
J
1

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) })
  }
}
Jahncke answered 17/12, 2020 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.