Async closure with borrowed arg

There is actually a way to do this which is discussed at Higher function taking async reference-taking closures.

In short you need to define a trait that specifies both the output type and the future type:

pub trait AsyncFnOnce<A> {
    type Output;
    type Future: Future<Output = Self::Output>;

    fn call(self, arg: A) -> Self::Future;
}

Then make a blanket implementation for closures that have the correct signature:

impl<A, C, F> AsyncFnOnce<A> for C
where
    C: FnOnce(A) -> F,
    F: Future,
{
    type Output = F::Output;
    type Future = F;

    fn call(self, arg: A) -> Self::Future {
        self(arg)
    }
}

The in_browser function can then have the following signature:

pub async fn in_browser<F>(f: F)
where
    F: for<'a> AsyncFnOnce<&'a WebDriver, Output = WebDriverResult<()>>,

This does currently have some limitations since the compiler isn't smart enough to infer a closures type if an Fn trait isn't used as a bound on the function that uses the closure. Therefore the above method can only be given functions and not closures. So the following would work while the original in_browser call using a closure wouldn't:

async fn explict_callback(driver: &WebDriver) -> WebDriverResult<()> {
    // do something with the borrowing
    driver.get("http://localhost:8000/newShortcut").await?;

    Ok(())
}
in_browser(explict_callback).await;

Here is a playground that mocks the problem and here is another playground which "solves" it using the above method.

2 Likes