4

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?).

6
  • Since this is a common sense, in case if you missed it, i want to point: 'static lifetime boundary doesn't mean that it is going remain until the process terminates. 'static boundary usually used to request ownership of the variable. Commented Feb 10, 2020 at 7:45
  • I'd rather not 'static something that doesn't need it, but I don't see how to do that. We need an owner since we don't exactly know when asycn fn will be executed. Simply we can't be sure that the borrowed argument will live on that moment. It needs to be static or owned or unmovable(refering Pins in here, which i have superficial knowledge about it) Commented Feb 10, 2020 at 7:51
  • While the explanation in your second comment is correct, I disagree with your first statement 'static boundary usually used to request ownership of the variable. A 'static lifetime bound means the borrow has 'static lifetime, meaning the entire duration of the program. It is still a borrow though. Commented Feb 10, 2020 at 8:11
  • Do reading this address your issue? Commented Feb 10, 2020 at 8:31
  • @L.Riemer It may not cover the whole concept but i don't think it is wrong, It basically ensures that H can be a argument type as String not a &String.(assuming String has implemented Fn). If you really need a static borrow you need to define your parameter like : &'static H Commented Feb 10, 2020 at 8:37

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

7 Comments

I'm aware that input lifetimes to async fns end up "on" the impl Future; I mostly follow your answer. <s>But you say, "this gets you to another problem" "body does not live long enough" — and that body must outlive request_handler — why must it outlive request_handler? I would only expect it to need to outlive the future that request_handler returns, no?</s> Oh, b/c you've removed the HRTB. Yes, w/o the HRTB, I agree, body does not live long enough / I agree w/ the tail end of your post. That was what pushed me to HRTB; but why does it not work w/ the HRTB?
As for your recommendation to Box trait objects… would you mean the Fn or the AsyncRead, or both? The Fn would probably be considerably simpler if Boxed, and I think I'll do that. The AsyncRead has a reference to something handle_request needs, unfortunately, and that function makes use of it after the callback (request_handler).
It does not work with HRTB because the arg ofprocess_requests(&handler) depends on an AsyncRead reference that depends on a bound lifetime 'b: async fn handler<'b>(..., body: &'b mut (dyn AsyncRead + 'b)) But in your process_request you are declaring an HRTB constraint for<'a>Fn(crate::Request, &'a mut (dyn AsyncRead + 'a)) that is incompatible with the "single" bounded lifetime requirement stated with the def of handler.
I was generic about the Boxed trait suggestion because it really depends on your requirements.
I'm still not following the reasoning on why the HRTB example doesn't work. You call it a "bound lifetime", but in what way (particularly at the site of the error, which is is it "bound"? Is it not "bound" when I finally pass in a &dyn AsyncRead, at which point the lifetime is "bound" to that of the concrete AsyncRead that I am passing in? Will it not work as written, or at all? If not at all, how it not a bug in the language that I can't pass this function as a generic argument?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.