Async function with generic Fn parameter

It seems borrow checker cannot overcome cases below:

use std::future::Future;

#[derive(Debug, Default)]
pub struct Container {
    v: Vec<u8>,
}

impl Container {
    async fn mutation(&mut self) {
        self.v.push(42);
    }

    async fn mutations(&mut self) {
        let n = self.v.len() + 1;
        for _ in 0..n {
            self.mutation().await;
        }
    }

    async fn generic_mutations<F, Fut>(&mut self, f: F)
    where
        F: Fn(&mut Self) -> Fut,
        Fut: Future<Output = ()>,
    {
        let n = self.v.len() + 1;
        for _ in 0..n {
            f(self).await;
        }
    }

    async fn generic_mutations_with_lifetime<'a, F, Fut>(&'a mut self, f: F)
    where
        F: Fn(&'a mut Self) -> Fut,
        Fut: Future<Output = ()>,
    {
        let n = self.v.len() + 1;
        for _ in 0..n {
            f(self).await;
        }
    }
}

#[tokio::main]
async fn main() {
    // compiled
    {
        let mut c = Container::default();
        c.mutations().await;
        c.mutations().await;
        println!("{:?}", c);
    }

    // error: lifetime may not live long enough
    //     --> src/main.rs:54:35
    //     |
    //     54 |         c.generic_mutations(|con| con.mutation()).await;
    // |                              ---- ^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
    // |                              |  |
    // |                              |  return type of closure `impl Future<Output = ()>` contains a lifetime `'2`
    // |                              has type `&'1 mut Container`
    {
        let mut c = Container::default();
        c.generic_mutations(|con| con.mutation()).await;
        c.generic_mutations(|con| con.mutation()).await;
        println!("{:?}", c);
    }

    // error[E0499]: cannot borrow `*self` as mutable more than once at a time
    //     --> src/main.rs:38:15
    //     |
    //     31 |     async fn generic_mutations_with_lifetime<'a, F, Fut>(&'a mut self, f: F)
    // |                                              -- lifetime `'a` defined here
    //     ...
    //     38 |             f(self).await;
    // |             --^^^^-
    //     |             | |
    //     |             | `*self` was mutably borrowed here in the previous iteration of the loop
    // |             argument requires that `*self` is borrowed for `'a`
    {
        let mut c = Container::default();
        c.generic_mutations_with_lifetime(|con| con.mutation())
            .await;
        c.generic_mutations_with_lifetime(|con| con.mutation())
            .await;
        println!("{:?}", c);
    }
}

How can I make use of the generic while still tell the borrow checker that I'm doing things right?

What I'd like to achieve is passing a generic async function(closure) as a parameter to a given framework which can be arbitrarily combined.

I must pass &mut self because there're many case you cannot move ownership and you do want to mutate the state. Or it's offered by a library, for example, tokio's AsyncRead.

I'm sure there's only one writer so a mutex seems overhead. Also, as most lock are not re-enterable, nested async functions(closures) may cause deadlock.

Not sure if I understand the problem very well (async lifetimes still confuse me a lot). But I would assume you want something like that (which doesn't work either):

     async fn generic_mutations_with_lifetime<'a, F, Fut>(&'a mut self, f: F)
     where
-        F: Fn(&'a mut Self) -> Fut,
+        for<'b> F: Fn(&'b mut Self) -> Fut, // where Fut: 'b, but how to express that?
         Fut: Future<Output = ()>,
     {

(Playground)

P.S.: Or isn't it the other way around: lifetime 'b would need to outlive Fut? Maybe my example was wrong.

Unfortunately, this is not really possible with the where bounds that exist today. There's a hack using a helper trait, but it only works when you use a real function, and not when the function is a closure.

The playground isn't compiled.

Also with 'a lifetime parameter shown above, if there is no iteration, it can compile.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.