Async callback signature

Hello,

I have an async function :

async fn handle(_req: Request<Body>) ->  Result<Response, Infallible> {
    ...
}

And the hyper executor :

pub async fn run_service(addr: SocketAddr, rx: Receiver<()>) -> impl Future<Output = Result<(), hyper::Error>> {
    let new_service = make_service_fn(move |_| {
        async {
            Ok::<_, Error>(service_fn(move |req| {
                handle(req)
            }))
        }
    });
    let server = Server::bind(&addr).serve(new_service);

    server.with_graceful_shutdown(async {
        rx.await.ok();
    })
}

And I would like to pass the handle function as a callback to run_service. So I added a parameter :

pub async fn run_service(
    addr: SocketAddr,
    rx: Receiver<()>,
    handle: Box<dyn Fn(Request<Body>) -> Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>> + Send + Sync>,
) -> impl Future<Output = Result<(), hyper::Error>> {
...
}

For the type I've been inspired by this thread. And when I try to compile it says :

error[E0507]: cannot move out of `handle`, a captured variable in an `FnMut` closure
  --> src/lib.rs:49:15
   |
46 |       handle: Box<dyn Fn(Request<Body>) -> Pin<Box<dyn Future<Output = Result<Response, Infallible>> + Send>> + Send + Sync>,
   |       ------ captured outer variable
...
49 |           async {
   |  _______________^
50 | |             Ok::<_, Error>(service_fn(move |req| {
51 | |                 handle(req)
   | |                 ------
   | |                 |
   | |                 move occurs because `handle` has type `Box<dyn Fn(Request<Body>) -> Pin<Box<dyn futures::Future<Output = Result<hyper::Response<Body>, Infallible>> + std::marker::Send>> + Sync + std::marker::Send>`, which does not implement the `Copy` trait
   | |                 move occurs due to use in generator
52 | |             }))
53 | |         }
   | |_________^ move out of `handle` occurs here

I can see that the Response is moved out as return value, but I don't understand why the function would have to be moved out.

How could I solve this issue ?

Thank you for the help.

Bruno Thomas

async fn should not return impl Future, that will give you two layers of Future. The appropriate signature here looks like async fn run_service(/* ... */) -> Result<(), hyper::Error>.

It's because the expression handle(req) occurs inside a move closure, meaning the closure takes ownership of the captured variable handle. Typically you work around this by explicitly borrowing:

+   let borrowed_handle = &handle;
    let new_service = make_service_fn(move |_| {
        async {
            Ok::<_, Error>(service_fn(move |req| {
-               handle(req)
+               borrowed_handle(req)
            }))
        }
    });

That way instead of moving handle into the closure, you just copy the borrow. Applying that change might lead to errors caused by new_service no longer implementing 'static (since it will contain a borrow of the local variable handle). If that happens, you should switch to using

Arc<dyn Fn(Request<Body>) -> BoxFuture<'static, Result<Response, Infallible>> + Send + Sync>

for handle in place of the current type. (BoxFuture is just a type alias from the futures crate that makes this a little shorter.) Then you can clone the Arc and move the clone wherever it's needed, without making your closures non-'static:

    let new_service = make_service_fn(move |_| {
+       let cloned_handle = Arc::clone(&handle);
        async {
            Ok::<_, Error>(service_fn(move |req| {
-               handle(req)
+               cloned_handle(req)
            }))
        }
    });

(Disclaimer: not tested.)

1 Like

Awesome. Thank you for your time and explanations. You make me learn.

I made it work with the following function call :

run_service(addr,  rx, Arc::new(|req: Request<Body>| {Box::pin(handle(req))})

For the record, this SO thread helped also to understand.

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.