如何使用异步闭包捕获和接受引用

Dem*_*gos 5 closures asynchronous lifetime rust

我试图传递一个返回Future<Output=bool>到异步函数的闭包,并将此闭包称为异步谓词(类似于异步.filter或其他高阶函数)。

该谓词接收其输入作为引用。我找到了如何为不捕获其环境的纯谓词实现它:

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
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

仅支持非捕获闭包是相当有限的。我想使用一个捕获其环境的闭包。通过更改函数的边界compute_and_check,我能够传递使用其环境的闭包 - 但不能传递其输入:

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
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

我可以编写一种实现,其中闭包使用其输入,以及闭包使用其环境。但不能同时两者。

我如何接受一个使用其输入和环境引用的未来生产闭包?

我想写的是这样的:

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
}

Run Code Online (Sandbox Code Playgroud)

游乐场链接

此代码无法编译,因为我要求闭包返回BoxFuture<'r + 'a, bool>,但这不是合法的语法:

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>
   |                                                   ^^
Run Code Online (Sandbox Code Playgroud)

据我了解,我的问题的核心是我需要限制我的高级特质束缚。'r我想说的不是“对于任何”,而是“对于任何'r不会过时的'a”,但我不知道如何写下来。

我尝试对我的BoxFuture类型别名使用两个生命周期和限制,或者定义一个辅助特征,但我没有设法解决这个问题,因为我未能对 HRTB 生命周期应用限制。

compute_and_check1为了完整起见,以下是将最终闭包传递给(仅输入)和compute_and_check2(仅环境)时出现的错误:

  • 使用compute_and_check1(仅输入)playground 链接

    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>>`
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用compute_and_check2(仅环境)playground 链接

    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`
    
    Run Code Online (Sandbox Code Playgroud)

我还研究了夜间功能unboxed_closure但未能解决我的问题。我希望我的代码能够在稳定的 Rust 上运行,但如果这是唯一的解决方案,我的代码要求 nightly 也是可以接受的。