Help me get this to compile, please

Once again I attempt something that I find trivial in Scala and end up beating my head against a wall for a half hour trying to do it in Rust. :slight_smile:

I just want to write a general fn to use backoff::retry to retry an async operation, as a sync op, meaning running the inner op with task::block_in_place.

IOW, the operation (f) is just any async fn.

Here's one of the things I've tried:

    pub fn run_with_retry<F, B, T, E>(backoff: ExponentialBackoff, f: F) -> Result<T, Error<E>>
    where
        F: FnMut() -> Future<Output = Result<T, Error<E>>>,
    {
        retry(backoff, || {
            task::block_in_place(move || runtime::Handle::current().block_on(f))
                .map_err(backoff::Error::transient)
        })
    }

The error here:

error[E0782]: expected a type, found a trait
  --> util/src/future_runner.rs:33:23
   |
33 |         F: FnMut() -> Future<Output = Result<T, Error<E>>>,
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: you can add the `dyn` keyword if you want a trait object
   |
33 |         F: FnMut() -> dyn Future<Output = Result<T, Error<E>>>,

Following the compiler directive, and then another one, we get to

    pub fn run_with_retry<F, B, T, E>(backoff: ExponentialBackoff, f: F) -> Result<T, Error<E>>
    where
        F: FnMut() -> (dyn Future<Output = Result<T, Error<E>>>) + std::future::Future,
    {
        retry(backoff, || {
            task::block_in_place(move || runtime::Handle::current().block_on(f))
                .map_err(backoff::Error::transient)
        })
    }

First, I cannot grok why such type bounds are necessary: Why does F have to be both dyn Future and Future?

And that still doesn't compile, as map_err doesn't exist on Future.

Yet, I can do this, directly:

let result = retry(backoff(), || {
    block_in_place(|| {
        Handle::current().block_on(
            <some_async_fn>(),
        )
    })
    .map_err(backoff::Error::transient)
});

Very often I find it difficult to take a bit of Rust code and abstract it into something general for reasons along these lines. It seems the types the compiler is working with (inferring) are often quite complex.

Can someone steer me in the right direction here?

Instead of forcing your callback to return a (boxed) trait object, you probably want to use a generic parameter for the future type that F returns:

    pub fn run_with_retry<F, Fut, B, T, E>(backoff: ExponentialBackoff, f: F) -> Result<T, Error<E>>
    where
        F: FnMut() -> Fut,
        Fut: Future<Output = Result<T, Error<E>>>,
    {
        retry(backoff, || {
            task::block_in_place(move || runtime::Handle::current().block_on(f))
                .map_err(backoff::Error::transient)
        })
    }
1 Like

You haven't stated which executor you are using, but I guess it's tokio. Handle::block_on() takes a Future, not a function that outputs a Future. The second error you get is trying to point you to that, and the solution is to either just make f implement Future or call f inside the call to block_on. This can be seen in the case at the end that works, since you are calling the async function.

2 Likes

Ok, I've got it. (I'm happy with String in the error channel for now):

    pub fn run_with_retry<F, Fut, B, T, E>(
        backoff: ExponentialBackoff,
        mut f: F,
    ) -> Result<T, String>
    where
        F: FnMut() -> Fut,
        Fut: Future<Output = Result<T, E>>,
        E: Debug,
    {
        retry(backoff, || {
            task::block_in_place(|| runtime::Handle::current().block_on(f()))
                .map_err(backoff::Error::transient)
        })
        .map_err(|e| format!("{:?}", e))
    }

Actually, simplified to this (there's no need for type param B):

    pub fn run_with_retry<F, Fut, T, E>(backoff: ExponentialBackoff, mut f: F) -> Result<T, String>
    where
        F: FnMut() -> Fut,
        Fut: Future<Output = Result<T, E>>,
        E: Debug,
    {
        retry(backoff, || {
            task::block_in_place(|| runtime::Handle::current().block_on(f()))
                .map_err(backoff::Error::transient)
        })
        .map_err(|e| format!("{:?}", e))
    }