Passing self to callback returning future vs lifetimes

This kind of code with async function-typed parameters that take reference arguments is unfortunately (currently) notoriously hard to get to compile. As far as I’m aware, due to limitations in the type checker this is at the moment only possible if you introduce some extra helper traits and also only if you don’t actually need support for closures in the call to .call_it but are okay with using only fns, which don’t support capturing anything. Then it works like this:

use futures::executor::block_on;
use std::future::Future;

pub trait AsyncSingleArgFnOnce<Arg>: FnOnce(Arg) -> <Self as AsyncSingleArgFnOnce<Arg>>::Fut {
    type Fut: Future<Output=<Self as AsyncSingleArgFnOnce<Arg>>::Output>;
    type Output;
}

impl<Arg, F, Fut> AsyncSingleArgFnOnce<Arg> for F
where
    F: FnOnce(Arg) -> Fut,
    Fut: Future,
{
    type Fut = Fut;
    type Output = Fut::Output;
}

pub struct Doer {}

impl Doer {
    pub async fn do_it(&mut self) {
        println!("do it!");
    }

    pub async fn call_it<F>(&mut self, f: F)
    where
        F: for<'a> AsyncSingleArgFnOnce<&'a mut Doer, Output = ()>,
    {
        self.do_it().await;
        f(self).await;
        self.do_it().await;
    }
}


fn main() {
    let mut d = Doer {};
    block_on(async {
        d.call_it({
            async fn f(a: &mut Doer) {
                a.do_it().await;
                a.do_it().await;
            }
            f
        }).await;
    });
}

(playground)

Compare this answer and also this thread for similar kinds of questions.

4 Likes