Thanatos
Thanatos

Reputation: 44256

How do I pass an async callback containing an argument that is a reference?

I have a function that requires an asynchronous callback (a request handler); I'm currently trying to accept things that look like this:

async fn handle_request<'a>(request: Request, body: &'a mut (dyn AsyncRead + 'a)) -> HandlerResponse

It had been working up until the addition of the second parameter body, which is causing me grief. The function that accepts the parameter looks like this:

pub async fn process_requests<H, F>(
    mut connection: Box<dyn AsyncConnection>,
    request_handler: &H,
) -> Result<(), DecodeError>
where
    for<'a> H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a,
    F: Future<Output = HandlerResponse>,
{

Part way through this function, we call a helper function:

handle_request(&mut connection, request_handler, request)

which has a very similar signature; in particular, the signature for request_handler is identical. It does some minor pre-processing before invoking request_handler. When I attempt to compile this, I get:

error[E0310]: the parameter type `H` may not live long enough
    |
106 | pub async fn process_requests<H, F>(
    |                               - help: consider adding an explicit lifetime bound `H: 'static`...
...
142 |                     handle_request(&mut connection, request_handler, request)
    |                     ^^^^^^^^^^^^^^
    |
note: ...so that the type `H` will meet its required lifetime bounds
    |
142 |                     handle_request(&mut connection, request_handler, request)
    |                     ^^^^^^^^^^^^^^

Why do I need this / what do I do about this? Indeed adding 'static to H: in the where does seem to silence the error, but is that the right thing to do? Couldn't the type implementing H carry a reference, and 'static would forbid that? I don't necessarily want to do that, but any amount of trying to annotate a lifetime that isn't 'static onto H has not worked; e.g., 'a + Fn(...) -> F + 'a does not work, nor does adding a new generic 'b lifetime. I'd rather not 'static something that doesn't need it, but I don't see how to do that.

(I'm also a bit perplexed by the wording of the message — that the parameter type — not some argument or variable — doesn't live long enough. How does a type not live long enough?)

I've played with things a bit more, but I still can't get anything that actually compiles. This playground example shows another one of the more perplexing error messages that I'm running into. I've tried dropping some of the lifetime annotations, and moved the for<'a> bit (I'm not sure what the difference is?).

Upvotes: 4

Views: 2675

Answers (1)

attdona
attdona

Reputation: 18923

A callback argument passed as reference does not work with HRTB constraints when the callback is marked with the async keyword.

The signature using async/await:

async fn handle_request<'a>(request: Request, body: &'a mut (dyn AsyncRead + 'a)) -> HandlerResponse

Is equivalent to:

fn handle_request<'a>(request: Request, body: &'a mut (dyn AsyncRead + 'a)) -> Future<Output=HandlerResponse> + 'a

This implies that input lifetimes of an async function are captured in the future returned by the async function.

See the paragraph "Lifetime capture in the anonymous future" of RFC 2394.

Declaring the function that accepts the parameter as:

pub async fn process_requests<H, F>(
    mut connection: Box<dyn AsyncConnection>,
    request_handler: &H,
) -> Result<(), DecodeError>
where
    for<'a> H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a,
    F: Future<Output = HandlerResponse>,
{

Give a compilation error because the HRTB requirement:

for<'a> H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a

"unlink" the lifetime bound from the caller and produce the compilation error expected bound lifetime parameter 'a, found concrete lifetime

for more details about HRTB see here.

To make it works you have to write:

pub async fn process_requests<'a, H, F>(
    mut connection: Box<dyn AsyncConnection>,
    request_handler: &H,
) -> Result<(), DecodeError>
where
    H: Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) -> F + 'a,
    F: Future<Output = HandlerResponse>,
{

But this get you to another problem:

`body` does not live long enough

because the local body struct does not outlive request_handler:

async fn handle_request<'a, H, F>(
    request_handler: &H,
    request: Request,
) -> io::Result<()>
where
    H: Fn(Request, &'a mut (dyn AsyncRead + 'a)) -> F,
    F: Future<Output = String>,
{
    let mut body = Body {};
    request_handler(request, &mut body);
    unimplemented!();
}

If feasible one possible solution could be to use Boxed trait objects and get rid off HTRB constraints.

Upvotes: 2

Related Questions