1

I am trying to get the following simple apply function to compile (simlified for example):

struct Context(i32);

pub async fn apply<F, Fut>(ctx: &mut Context, f: F)
where
    F: Fn(&mut Context) -> Fut,
    Fut: Future<Output = ()>,
{
   
    for _ in 0..10 {
        f(ctx).await;
    }
}



async fn example() {
    let mut ctx = Context(0); 
    apply(
        &mut ctx, 
        |ctx| async {
            ctx.0 += 1; 
        }
    ).await;
    assert_eq!(ctx.0, 10);
}

Playground

Unfortunately, I am getting the following error in my example function:

error: lifetime may not live long enough
  --> src/lib.rs:20:15
   |
20 |           |ctx| async {
   |  __________----_^
   | |          |  |
   | |          |  return type of closure `{async block@src/lib.rs:20:15: 20:20}` contains a lifetime `'2`
   | |          has type `&'1 mut Context`
21 | |             ctx.0 += 1; 
22 | |         }
   | |_________^ returning this value requires that `'1` must outlive `'2`


My attempt at fixing this was as follows:

pub async fn apply<'a: 'b, 'b, F, Fut>(ctx: &'a mut Context, f: F)
where
    F: Fn(&'b mut Context) -> Fut,
    Fut: Future<Output = ()>,
{
   
    for _ in 0..10 {
        f(ctx).await;
    }
}

Playground

Now example is happy, but I'm unfortunately getting an new error in apply:

error[E0499]: cannot borrow `*ctx` as mutable more than once at a time
  --> src/lib.rs:10:11
   |
3  | pub async fn apply<'a: 'b, 'b, F, Fut>(ctx: &'a mut Context, f: F)
   |                            -- lifetime `'b` defined here
...
10 |         f(ctx).await;
   |         --^^^-
   |         | |
   |         | `*ctx` was mutably borrowed here in the previous iteration of the loop
   |         argument requires that `*ctx` is borrowed for `'b`

How can I get the semantics of apply to compile?

I looked at some similar questions about async functions as parameters but couldn't seem to find one where the closure requires a context parameter and is also called multiple times.

3
  • 1
    Using async Fn(&mut Context) works but doesn't have quite the same semantics as prescribing &'b mut Context as the parameter. Commented Aug 22 at 21:32
  • @cafce25 That's awesome! Glad to see Rust improving on this axis. Is there something equivalent I could use on stable? Commented Aug 22 at 21:49
  • 1
    AsyncFn is stable since 1.85 Commented Aug 23 at 12:15

1 Answer 1

2

EDIT: As pointed out by BallpointPen, AsyncFn has also been stabilized in 1.85, so the following simple solution works!:

pub async fn apply<'a, F>(ctx: &'a mut Context, f: F)
where
    F: AsyncFn(&mut Context),
{
    for _ in 0..10 {
        f(ctx).await;
    }
}

Original Answer:

Ok, I figured It out myself. Using async closures, stabilized in 1.85, the code snippet below works as intended.

Even without async closures, the Applicable trait as defined here will work for proper async functions, though unfortunately not for || async {} blocks.


trait Applicable<'a> {
    type Fut: Future<Output = ()> + 'a;
    fn call(&mut self, ctx: &'a mut Context) -> Self::Fut;
}

impl<'a, F, Fut> Applicable<'a> for F
where
    F: FnMut(&'a mut Context) -> Fut,
    Fut: Future<Output = ()> + 'a,
{
    type Fut = Fut;
    fn call(&mut self, ctx: &'a mut Context) -> Self::Fut {
        (*self)(ctx)
    }
}

struct Context(i32);

async fn apply(ctx: &mut Context, mut f: impl for<'a> Applicable<'a>) {
    for _ in 0..10 {
        f.call(ctx).await;
    }
}

async fn example() {
    let mut ctx = Context(0); 
    apply(
        &mut ctx, 
        async |ctx: &mut Context| {
            ctx.0 += 1; 
        }
    ).await;
    assert_eq!(ctx.0, 10);
}
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.