Use self in a thread closure

Hello,

I have this extremely simplified Example that looks as follows:

use std::thread::spawn;

struct Service {}

impl Service {
    fn run_tasks(&self) {
        for i in 1..5 {
            spawn(move || self.transition(i));
        }
    }

    fn transition(&self, i: i32) {
        println!("Hello {}", i);
    }
}

fn main() {
    let service = Service {};
    service.run_tasks();
}

That fails to compile with the following error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> src/main.rs:8:19
  |
8 |             spawn(move || self.transition(i));
  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 6:5...
 --> src/main.rs:6:5
  |
6 | /     fn run_tasks(&self) {
7 | |         for i in 1..5 {
8 | |             spawn(move || self.transition(i));
9 | |         }
10| |     }
  | |_____^
  = note: ...so that the types are compatible:
          expected &Service
             found &Service
  = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/main.rs:8:19: 8:45 self:&Service, i:i32]` will meet its required lifetime bounds
 --> src/main.rs:8:13
  |
8 |             spawn(move || self.transition(i));
  |             ^^^^^

What i am trying to do is create a Service instance which would initialize a state machine and would be responsible for running a bunch of threads (or futures) to perform certain tasks. Once each of those tasks completes, it would transition the state machine

So my understanding is that the closure would capture the ownership of variables from the scope in which the closure is created. Rust wants to make sure that self will love as long as the thread is running which is why it is requiring the 'static lifetime. Provided that self is a shared reference, i am not totally sure what to do about this.

I could add 'static to self in the function signature to be &'static self but this will lead the variable to be a global one to satisfy the compiler.

How would i go about fixing my code in a way to satisfy the compiler?

Note: I am using tokio in my original code base but this exact error is what i am seeing.

You need a scoped spawn to limit the thread closure's lifetime to the borrow of &self. You could try a single crossbeam::scope around that for loop, or similar with rayon::scope with its thread pool.

2 Likes

Indeed Service {} will live less than spawned threads, since program will exit after all logic in main completed. Also since threads will be eventually mutating state of Service mutex shall be used to avoid race condition. If you do not need to execute code after run_tasks or mutate Service state @cuviper 's solution is much simpler. Otherwise you will need something more complicated like below:

use std::thread::{spawn, JoinHandle};
use std::sync::{Arc, Mutex};

struct Service {}

impl Service {
    fn run_tasks(this: Arc<Mutex<Service>>) -> Vec<JoinHandle<()>> {
        // Thread JoinHandle will be required below to wait for all threads complete
        let mut handles = Vec::new();
        for i in 1..5 {
            let copy = this.clone();
            handles.push(spawn(move || copy.lock().unwrap().transition(i)));
        };
        handles
    }
    fn transition(&self, i: i32) {
        println!("Hello {}", i);
    }
}

fn main() {
    let service = Arc::new(Mutex::new(Service {}));
    let handles = Service::run_tasks(service);
    // do some stuff here ...
    // then wait until all threads complete before program exit
    for handle in handles.into_iter() {
        handle.join().expect("thread complete")
    }
}
2 Likes

Thanks. I ended up using the associated method approach provided that with scope i had issues with members of the service struct "Cannot be shared with threads safely"

1 Like