How to do an "fire and forget" call in rust?

Viewed 321

What I would like to achieve using the Rust language is to execute a closure or method after X seconds (without waiting for it). Being more used to languages like C#, a simple solution would be to spawn a thread, sleep for X seconds, and execute whatever needs to be done.

I tried this:

fn fire_and_forget(&self) {
    thread::spawn(|| {
        sleep(Duration::from_secs(10));
        self.do_something();
    });
}

Obviously, this doesn't work. Depending on my context, I get errors like error[E0277]: cannot be sent between threads safely or error[E0759]: self has an anonymous lifetime '_ but it needs to satisfy a 'static lifetime requirement.

I hope there is solution for this. This "context" I am referring to is quite specific: this fire_and_forget method will be called from the outside so I am not sure I can change its signature with generic lifetimes.

Any idea if there is a solution / workaround / unsafe way for this?

1 Answers

If you check out thread::spawn's signature, you'll see that the closure is required to have a 'static lifetime. This means that it must own all of its data and can not borrow from its environment. This rule exists because there is nothing that guarantees that the parent thread will outlive the newly spawned thread and thus there's no guarantee that any references inside the thread::spawn closure stay valid.

There are a few work-arounds here, although they depend on the structure of self and your program logic. They all boil down to getting owned data in the end and moving that owned data into the closure.

Let's assume you want to capture self's state at the time of calling fire_and_forget and Self: Clone. In that case you can write the following:

let slf = self.clone(); // obtain an owned version of `slf`
thread::spawn(move || { // `move` makes the closure take ownership of `slf`
    sleep(Duration::from_secs(10));
    slf.do_something() // borrows data from the closure, not the environment
})

If Self doesn't implement Clone or it's not okay to use the old state, you'll need some kind of synchronization / shared access to its current state, e.g. achievable through Arc<Mutex<State>>. Then you'll have to clone the Arc, move it into the closure and call do_something with the shared reference to State.

struct State {
    
}
struct Foo {
    state: Arc<Mutex<State>>,
}
impl Foo {
    fn do_something(state: &State) {

    }

    fn fire_and_forget(&self) {
        let owned_state = Arc::clone(&self.state); // obtain an owned version of `state`
        thread::spawn(move || { // `move` makes the closure take ownership of `state`
            sleep(Duration::from_secs(10));
            let state = owned_state.lock().unwrap(); // get exclusive access to state
            Self::do_something(&*state) // borrows data from the closure, not the environment
        });
    
    }
}

Ultimately the solution will depend on your specific requirements, but as a general rule, a closure can't be 'static if it borrows from its environment.

Related