How to use an async closure both capturing and accepting references

Viewed 407

I am trying to pass a closure returning Future<Output=bool> to an async function and call this closure as an async predicate (something like an async .filter or other higher-order function).

This predicate receives its input as a reference. I found how to implement it for pure predicates that do not capture their environment:

type BoxFuture<'a, Out> = Pin<Box<dyn Future<Output=Out> + 'a + Send>>;

////////////////////////////////////////////////////////////////////////////////
// 1 -> Closure only uses the inner argument                                  //
////////////////////////////////////////////////////////////////////////////////

/// Run the computation and check that its output is not empty
fn run1<'a>(expected: &'a [u8]) -> impl Future<Output=()> + 'a {
  async move {
    let is_ok = compute_and_check1(|b: &[u8]| Box::pin(async move {
      b.len() > 0 // Only uses the inner argument
    }));
    let is_ok1 = is_ok.await;
    dbg!(is_ok1);
  }
}

/// Compute some bytes (may be complex / use async), then check their validity
/// with a user-supplied function, finally do some clean-up and return.
async fn compute_and_check1<F>(check: F) -> bool
  where
    F: for<'r> FnOnce(&'r [u8]) -> BoxFuture<'r, bool>
{
  let bytes = [0u8; 128];
  let is_ok = check(&bytes).await;
  drop(bytes);
  is_ok
}

Playground link

Supporting only non-capturing closures is quite limiting. I would like to use a closure that captures its environment. By changing the bounds of my compute_and_check function, I am able to pass a closure that uses its environment - but not its input:

type BoxFuture<'a, Out> = Pin<Box<dyn Future<Output=Out> + 'a + Send>>;

////////////////////////////////////////////////////////////////////////////////
// 2 -> Closure only uses the outer argument                                  //
////////////////////////////////////////////////////////////////////////////////

/// Run the computation and assume that its output is not empty if `expected` is not empty
fn run2<'a>(expected: &'a [u8]) -> impl Future<Output=()> + 'a {
  async move {
    let is_ok = compute_and_check2(|b: &[u8]| Box::pin(async move {
      expected.len() > 0 // Only uses the environment
    }));
    let is_ok2 = is_ok.await;
    dbg!(is_ok2);
  }
}

/// Compute some bytes (may be complex / use async), then check their validity
/// with a user-supplied function, finally do some clean-up and return.
async fn compute_and_check2<'a, F>(check: F) -> bool
  where
    F: for<'r> FnOnce(&'r [u8]) -> BoxFuture<'a, bool>
{
  let bytes = [0u8; 128];
  let is_ok = check(&bytes).await;
  drop(bytes);
  is_ok
}

Playground link

I can write an implementation where the closure uses its input, and one where the closure uses its environment. But not both at the same time.

How can I accept a Future-producing closure that uses references for both its input and environment?

What I would like to write is something like this:

type BoxFuture<'a, Out> = Pin<Box<dyn Future<Output=Out> + 'a + Send>>;

////////////////////////////////////////////////////////////////////////////////
// 3 -> Closure uses both the inner and outer arguments                       //
////////////////////////////////////////////////////////////////////////////////

/// Run the computation and check its output is the provided expected value
fn run3<'a>(expected: &'a [u8]) -> impl Future<Output=()> + 'a {
  async move {
    let is_ok = compute_and_check3(|b: &[u8]| Box::pin(async move {
      b == expected // Uses both the input and environment
    }));
    let is_ok2 = is_ok.await;
    dbg!(is_ok2);
  }
}

/// Compute some bytes (may be complex / use async), then check their validity
/// with a user-supplied function, finally do some clean-up and return.
async fn compute_and_check3<'a, F>(check: F) -> bool
  where
    F: for<'r> FnOnce(&'r [u8]) -> BoxFuture<'r + 'a, bool>
{
  let bytes = [0u8; 128];
  let is_ok = check(&bytes).await;
  drop(bytes);
  is_ok
}

Playground link

This code does not compile because I am asking the closure to return BoxFuture<'r + 'a, bool> but this is not legal syntax:

error[E0226]: only a single explicit lifetime bound is permitted
  --> src/main.rs:89:51
   |
89 |     F: for<'r> FnOnce(&'r [u8]) -> BoxFuture<'r + 'a, bool>
   |                                                   ^^

From what I understand, the core of my issue is that I need to restrict my Higher-Ranked Trait Bound. Instead of "for any 'r", I want to say "for any 'r that does not outlive 'a" but I don't see how to write it down.

I tried to use two lifetimes and restrictions to my BoxFuture type alias, or define an auxilliary trait but I did not manage to solve this issue because I fail to apply restrictions on the HRTB lifetime.

For completness, here are the errors I get when passing my final closure to compute_and_check1 (only input) and compute_and_check2 (only environment):

  • Using compute_and_check1 (only input) playground link

    error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
      --> src/main.rs:17:67
       |
    17 |       let is_ok = compute_and_check1(|b: &[u8]| Box::pin(async move {
       |  ___________________________________________________________________^
    18 | |       b == expected
    19 | |     }));
       | |_____^
       |
    note: first, the lifetime cannot outlive the lifetime `'a` as defined on the function body at 15:9...
      --> src/main.rs:15:9
       |
    15 | fn run3<'a>(expected: &'a [u8]) -> impl Future<Output=()> + 'a {
       |         ^^
    note: ...so that the types are compatible
      --> src/main.rs:17:67
       |
    17 |       let is_ok = compute_and_check1(|b: &[u8]| Box::pin(async move {
       |  ___________________________________________________________________^
    18 | |       b == expected
    19 | |     }));
       | |_____^
       = note: expected `(&[u8], &[u8])`
                  found `(&[u8], &'a [u8])`
    note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the body at 17:36...
      --> src/main.rs:17:36
       |
    17 |       let is_ok = compute_and_check1(|b: &[u8]| Box::pin(async move {
       |  ____________________________________^
    18 | |       b == expected
    19 | |     }));
       | |______^
    note: ...so that the expression is assignable
      --> src/main.rs:17:47
       |
    17 |       let is_ok = compute_and_check1(|b: &[u8]| Box::pin(async move {
       |  _______________________________________________^
    18 | |       b == expected
    19 | |     }));
       | |______^
       = note: expected `Pin<Box<dyn Future<Output = bool> + Send>>`
                  found `Pin<Box<dyn Future<Output = bool> + Send>>`
    
  • Using compute_and_check2 (only environment) playground link

    error: lifetime may not live long enough
      --> src/main.rs:17:47
       |
    17 |       let is_ok = compute_and_check2(|b: &[u8]| Box::pin(async move {
       |  ________________________________________-____-_^
       | |                                        |    |
       | |                                        |    return type of closure is Pin<Box<(dyn Future<Output = bool> + Send + '2)>>
       | |                                        let's call the lifetime of this reference `'1`
    18 | |       b == expected
    19 | |     }));
       | |______^ returning this value requires that `'1` must outlive `'2`
    

I have also looked into the unboxed_closure nightly feature but did not manage to solve my issue. I would prefer my code to work on stable Rust but it is acceptable for my code to require nightly if it is the only solution.

0 Answers
Related