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
}
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
}
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
}
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 linkerror[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 linkerror: 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.