Search code examples
rustaws-lambdaclosures

Why do I get a lifetime error when I store closure as a variable and no error when I declare it as an expression?


I'm using the AWS Lambda (via the lambda_http crate) and the following code doesn't compile:

let handler = service_fn(move |event: Request| async  {
    handler_organization_post(&shared_client, event).await
});
lambda_http::run(handler).await
error: lifetime may not live long enough
   --> organization/organization_post/src/main.rs:125:52
    |
125 |       let handler = service_fn(move |event: Request| async  {
    |  ______________________________---------------------_^
    | |                              |                   |
    | |                              |                   return type of closure `[async block@organization/organization_post/src/main.rs:125:52: 127:6]` contains a lifetime `'2`
    | |                              lifetime `'1` represents this closure's body
126 | |         handler_organization_post(&shared_client, event).await
127 | |     });
    | |_____^ returning this value requires that `'1` must outlive `'2`
    |
    = note: closure implements `Fn`, so references to captured variables can't escape the closure

However, if I simply inline the closure declaration into the call to lambda_http::run, the program compiles and runs with no problems:

lambda_http::run(service_fn(move |event: Request| async move {
    handler_organization_post(&shared_client, event).await
})).await

In my head, these are semantically equivalent, so what's going on? I'm clearly missing something and would appreciate a pointer in the right direction.


Solution

  • You need to use async move for the closure to be valid. This is the difference between the two versions of your code - the second uses async move, while the first does not.

    To understand this, recall that an async block evaluates to an impl Future - to some value which implements the Future trait. The code inside the block is not immediately executed. It is only executed when the future is “polled” - that is, when someone tries to evaluate the future and get the underlying value.

    Consider a very simple closure using async.

    let closure = |y: i32| async { y + 1 };
    

    This is actually not a valid closure. The async block does not have the move keyword, so the future it evaluates to contains a reference to y, not y itself. By the time the async block is evaluated, this reference will no longer be valid, since the closure will have returned. To make everything work, you need to change the code to

    let closure = |y: i32| async move { y + 1 };