Async closure with borrowed arg

Hi,

I'm trying to use a async closure into this async function:

pub async fn in_browser<F, Fut>(f: F)
where
    F: FnOnce(&WebDriver) -> Fut,
    Fut: Future<Output = WebDriverResult<()>>,
{

    // build driver
    let driver = WebDriver::new()

    match f(&driver).await {
        Ok(_) => (),
        Err(err) => log::error!("{:?}", err),
    };

    // Always explicitly close the browser. There are no async destructors.
    // .quit() consume self 
    driver.quit().await.expect("Driver quit failed");
}

// rocket tokio runtime
#[async_test]
async fn undefined_shortcut_return_a_form_to_create_a_shortcut() {
    in_browser(async move |driver: &WebDriver| {
        // do something with the borrowing
        driver.get("http://localhost:8000/newShortcut").await?;

        Ok(())
    })
    .await;
}

but I have this kind of error and I can't get out of this :confused: :

has type `&'1 thirtyfour::WebDriver`

lifetime may not live long enough
returning this value requires that `'1` must outlive `'2`

return type of closure `impl std::future::Future<Output = [async output]>` contains a lifetime `'2`

Can you help me pleas ?

Fwiw, you don't need the unstable async_closure feature to reproduce this. I believe the issue is that the future returned by the closure incorporates the borrow of the WebDriver that's passed in as an argument, but your bounds on in_browser don't allow this—you want something like

    for<'a> Fut<'a>: Future<Output = WebDriverResult<()>>,
    F: for<'a> FnOnce(&'a WebDriver) -> Fut<'a>,

but even with GATs I don't know of a way to express that in legal Rust. As usual with this kind of problem, though, you can work around it by erasing the return type:

    F: for<'a> FnOnce(&'a WebDriver) -> BoxFuture<'a, WebDriverResult<()>>,

(and then Box::pin in the appropriate place)

2 Likes

Thank you cole-miller ! :slight_smile:

What do you mean by Box::pin in the appropriate place ?
I tried

#[async_test]
async fn undefined_shortcut_return_a_form_to_create_a_shortcut() {
    let test = Box::pin(async move |driver: &WebDriver| {
        // do something with the borrowing
        driver.get("http://localhost:8000/newShortcut").await?;

        Ok(())
    });

    in_browser(test).await;
}

but compiler isn't happy :frowning: :

the method `boxed` exists for enum `std::result::Result<(), _>`, but its trait bounds were not satisfied
the following trait bounds were not satisfied:
`std::result::Result<(), _>: std::future::Future`
which is required by `std::result::Result<(), _>: rocket::futures::FutureExt`
`&std::result::Result<(), _>: std::future::Future`
which is required by `&std::result::Result<(), _>: rocket::futures::FutureExt`
`&mut std::result::Result<(), _>: std::future::Future`
which is required by `&mut std::result::Result<(), _>: rocket::futures::FutureExt`

Same with .boxed()

I'm probably doing something bad but just to know, is there other ways to work around like :

  • Passing a non borrowed value to the closure with something like Rc<RefCell<Option<WebDriver>>> and then get & replace to quit() on it ?
  • Using unsafe (don't know much about it)

(Other more 'dirty' ways could fit because it's about e2e testing purpose)

Note: when you're posting an error message please do cargo check in a terminal and copy-and-paste the full message, not just the scraps of it that appear in an IDE popup.

Anyway, that looks like the error message from trying to use FutureExt::boxed without importing FutureExt. These two snippets should both work:

    in_browser(|driver| Box::pin(async {
        ...
    })).await;
    use rocket::futures::future::FutureExt;
    in_browser(|driver| {
        async {
            ...
        }.boxed()
    }).await;

Note the placement: you are Pin<Box<...>>-ing the returned future, not the closure itself.

Erasing the return type of the callback is the solution I would recommend. I definitely would not use unsafe code.

1 Like

Oh Nice thanks a lot, it's working !

Yes Ok Pin<Box<...>> -ing the block that returns Future<Output = WebDriverResult<()>> (seems logic now but I could not have found it on my own).
If I try to rephrase: the previous work around was about 'a needed because the future capture &WebDriver so we have to tell the compiler that the &Webdriver live longer or as much as the future.
And the compiler can't handle that part so we use a Pin<Box<Fut>>.
Not sure about why compiler can't handle Fut or at least Box<Fut> ?

(Sorry for the error format I'll never do it again I promiss !)

So the deal is, there is no single type Fut such that your original closure implements FnOnce(&WebDriver) -> Fut. The closure is (or can be) "higher order", meaning it satisfies a bound like for<'a> FnOnce(&'a WebDriver) -> Fut<'a>, because the returned future is infected with the lifetime of the WebDriver borrow. But there's no way to refer to the actual (generic) return type of the closure, and there's no way (that I know of) for a function like in_browser to be generic, not over a normal type parameter like Fut, but over a whole generic "type family" like for<'a> Fut<'a>. You can get around this by casting the unnameable future into a dyn Future + 'a, but this is an unsized type and so can only be manipulated behind a pointer like Box. Then Pin is added on top because polling a future requires Pin<&mut Self>. (Pin itself is not a pointer and has no run-time cost, it just wraps another pointer in a restricted interface.) The BoxFuture type alias is just this:

type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

No worries, sorry if my previous post was unnecessarily harsh.

1 Like

Okay, thanks a lot for your explenation !

I don't think I want to do that there, but just to try to undestand more if you have time to answer :
So if I manage to implement Future<Output = WebDriverResult<()>> for a struct Fut<'a>(&'a WebDriver);
I could get rid of Box ?

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

Ahh, nice! I think I even ran into a similar situation (which I wouldn't have recognized as such without your post) before in this thread. Perhaps next time this comes up I will remember.

Indeed, if you wrote your own state machine and implemented Future manually instead of relying on an async block you could avoid the allocation/dynamic dispatch. It's a more advanced technique and I wouldn't know how to do it myself without reading some documentation first, so probably a good challenge!

1 Like

Ok sooo nice, thank you to both of you, i've learned a lot and I love your solutions :slight_smile: