Search code examples
rustrust-tokio

Tokio: "error: lifetime may not live long enough" when passing closure through channel


I'm attempting to pass an async closure between threads in a tokio based application, however I am running into a lifetime error I don't understand when trying to make this work.

I've created a minimal example of the issue in this playground:

use tokio; // 1.32.0

#[tokio::main]
async fn main() {
    
    let (send, recv) = tokio::sync::oneshot::channel();
    
    let val: u32 = 0;
    
    let func = move |foo: &u32| async move { val + foo };

    let _ = send.send(func);
    
    let fut = async move {
    
        let f = recv.await.expect("receiver should not fail");

        let foo: u32 = 1;
        let res = f(&foo).await;
        println!("Result: {}", res);
        assert_eq!(res, 1);
    };

    tokio::spawn(fut).await.expect("thread should succeed");

    
}

Which yields this error when compiling:

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/main.rs:10:33
   |
10 |     let func = move |foo: &u32| async move { val + foo };
   |                           -   - ^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                           |   |
   |                           |   return type of closure `[async block@src/main.rs:10:33: 10:57]` contains a lifetime `'2`
   |                           let's call the lifetime of this reference `'1`

error: could not compile `playground` (bin "playground") due to previous error

If I understand correctly, the error is telling me that the lifetime of the argument to the closure (foo) must outlive the return value of the closure. Why is this the case, why is it not satisfied, and how can I modify the code to avoid this error?


Solution

  • I believe this is a quirk of closure inference. Unfortunately, there is no good way to prevent it.

    If boxing is acceptable, you can box the returned future:

    fn generate_func(val: u32) -> impl Fn(&u32) -> Pin<Box<dyn Future<Output = u32> + '_ + Send>> {
        move |foo: &u32| Box::pin(async move { val + foo })
    }
    
    let func = generate_func(val);
    

    The extra function is to force the types on the compiler, because, again, closure inference is quirky.