Using thread::spawn inside loop (move of values)

Here's a shortened version of my code:

loop {
if start_button.is_high(){
   thread::spawn(move || {
   loop {
       run_stepper_motor(
         &mut menu, 
         &mut stepper_motor_dir, // <- Move occures here 
         &mut stepper_motor_step, // <- and here
         &mut sensor, // <- and here
        );
     }
   });
}
}

I'm using esp-idf-svc (with support for std), and I'm trying to make something like this above, but I don't know how can I fix this move issue. I have a few more functions in thread::spawn that say there is a move (above is only a short example). Here are some of the errors I'm getting:
(This values also don't implement clone trait)

error[E0382]: use of moved value: `stepper_motor_step`
   --> src/main.rs:228:27
    |
152 |     let mut stepper_motor_step = PinDriver::input_output(peripherals.pins.gpio18)?;
    |         ---------------------- move occurs because `stepper_motor_step` has type `esp_idf_svc::hal::gpio::PinDriver<'_, Gpio18, InputOutput>`, which does not implement the `Copy` trait
...
228 |             thread::spawn(move || {
    |                           ^^^^^^^ value moved into closure here, in previous iteration of loop
...
235 |                         &mut stepper_motor_step,
    |                              ------------------ use occurs due to use in closure

error[E0382]: use of moved value: `sensor`
   --> src/main.rs:228:27
    |
154 |     let mut sensor = PinDriver::input(peripherals.pins.gpio36)?;
    |         ---------- move occurs because `sensor` has type `esp_idf_svc::hal::gpio::PinDriver<'_, Gpio36, esp_idf_svc::hal::gpio::Input>`, which does not implement the `Copy` trait
...
228 |             thread::spawn(move || {
    |                           ^^^^^^^ value moved into closure here, in previous iteration of loop
...
236 |                         &mut sensor,
    |                              ------ use occurs due to use in closure

If you can't clone a value, you can wrap it in an Arc and pass a clone of that Arc in each iteration to the newly spawned thread. If you need mutable access to the value wrapped in the Arc, you can use an Arc<Mutex<T>> or Arc<RwLock<T>>.

1 Like

The issue is not purely in the code (i.e. in the way you're using move for example), but is a more fundamental issue in the logic you're trying to express. Consider for example this situation:

  • the outer loop starts
  • start_button.is_high() is true
  • a thread is spawned with references to menu, stepper_motor_dir, etc etc
  • the if ends and the outer loop starts again
  • start_button.is_high() is true again
  • another thread is spawned, also with references to menu, stepper_motor_dir, etc etc
    • you now have two threads with two mutable references to the same data, this cannot happen and the compiler will prevent you from doing this!

So you just cannot express this code. You need some way to prevent the multiple references from existing. This may be done for example by not running the inner loop in a different thread and instead blocking the current thread, or by deferring the check at runtime, for example by using a Mutex (at the cost however of potential deadlock or threads getting stuck if you use them in the wrong way).

There is also another problem with your code: what if the inner thread you spawned continues running after menu, stepper_motor_dir etc etc went out of scope? You now have references to some data that doesn't exist anymore, which the compiler will also prevent. This also needs to be addressed in some way, either by waiting for the thread to finish (std::thread::scope provides a nice way to do this that the compiler can see through), or by managing menu, stepper_motor_dir etc etc's lifetime at runtime, for example with an Arc.

Ultimately however there's no "true" general solution, they all depend on the details of what you're trying to code and the trade-offs you're willing to make.

4 Likes

So I already was thinking about this issue (I will probably do while == value statement instead of loop, and before thread I will add if start_thread == True thought I don't know if it will work yet), but thank you for suggesting some ways of how I can do it. :slight_smile:

Once you've made it only start once, you can do something like this:

let mut stepper_motor_dir = Some(stepper_motor_dir);
let mut stepper_motor_step = Some(stepper_motor_step);
let mut sensor = Some(sensor);

/* repaired loop and condition */ 
{
    thread::spawn({
        let stepper_motor_dir = stepper_motor_dir.take().unwrap();
        let stepper_motor_step = stepper_motor_step.take().unwrap();
        let sensor = sensor.take().unwrap();

        move || loop {
            run_stepper_motor(
                &mut menu,
                &mut stepper_motor_dir,
                &mut stepper_motor_step,
                &mut sensor,
            );
        }
    });
}