Search code examples
rustrust-tokiotrait-objects

Cannot relax lifetime of Trait Object


I have the following code:

use tokio; // 1.7.1
use futures::future::Future;
use std::pin::Pin;
use futures::FutureExt;
use std::marker::PhantomData;
use std::marker::Send;
use std::sync::Arc;
use async_trait::async_trait;

struct Orchestrator {
    worker: Box<dyn WorkerT<Box<dyn Fn(i32) -> Pin<Box<dyn Future<Output = i32> + Send>> + Send + Sync>> + Send + Sync>
}

impl Orchestrator {
    async fn other_things(&self, num: i32) -> i32{
        // Do some async stuff in here
        num+1
    }
    
    async fn main_stuff(self: Arc<Self>) {
        let func = |num: i32| {
            let slf = self.clone();
            async move {
                slf.other_things(num).await
            }.boxed()
        };
        
        self.worker.do_work(Box::new(func)).await;
    }
}

#[async_trait]
trait WorkerT<F: Fn(i32) -> Pin<Box<dyn Future<Output = i32> + Send>> + Send + Sync> {
    async fn do_work(& self, func: F);
}

struct Worker<F: Fn(i32) -> Pin<Box<dyn Future<Output = i32> + Send>> + Send + Sync> {
    phantom: std::marker::PhantomData<F>
}

#[async_trait]
impl<F: Fn(i32) -> Pin<Box<dyn Future<Output = i32> + Send>> + Send + Sync> WorkerT<F> for Worker<F> {
    async fn do_work(& self, func: F) {
        for i in 0..5 {
            func(i).await;
        }
    }
}


#[tokio::main]
async fn main() {
    let orchestrator = Arc::new(Orchestrator { worker: Box::new(Worker { phantom: PhantomData }) });
    orchestrator.main_stuff().await;
}

With the following error:

error[E0597]: `self` does not live long enough
  --> src/main.rs:22:23
   |
21 |         let func = |num: i32| {
   |                    ---------- value captured here
22 |             let slf = self.clone();
   |                       ^^^^ borrowed value does not live long enough
...
28 |         self.worker.do_work(Box::new(func)).await;
   |                             -------------- cast requires that `self` is borrowed for `'static`
29 |     }
   |     - `self` dropped here while still borrowed

Currently, because the default lifetime of dyn WorkerT... is 'static, it requires that the borrow of self in main_stuff be 'static. I need to relax the lifetime of the worker field but I don't know how. If I change worker to be of type 'Workerinstead ofdyn WorkerT...` this works, but I would much prefer keep it as the trait.

I have tried adding a lifetime to the Orchestrator struct and then giving that lifetime to the worker field, but then it says that my created lifetime needs to outlive 'static.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=7f34fa394d706409942659f1d8ce36c0


Solution

  • You need to clone the Arc before creating the closure so that you can give an Arc to the closure. What you have in your code tries to borrow self, which won't outlive the closure. Cloning the Arc solves this issue as the closure can now own its own Arc pointing at the same value.

    For example:

        async fn main_stuff(self: Arc<Self>) {
            let self2 = self.clone();
            let func = move |num: i32| {
                let self3 = self2.clone();
                async move {
                    self3.other_things(num).await
                }.boxed()
            };
            
            self.worker.do_work(Box::new(func)).await;
        }
    

    The inner clone is still required so that the type of func implements Fn (otherwise it will implement FnOnce due to moving a captured variable into the future).