Thread join vs. winit closure

error[E0507]: cannot move out of `loaderthread`, a captured variable in an `FnMut` closure
   --> src/
278 |     let loaderthread = start_content_thread(
    |         ------------ captured outer variable
470 |             loaderthread.join();                                // wait for loader thread
    |             ^^^^^^^^^^^^ move occurs because `loaderthread` has type `std::thread::JoinHandle<()>`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.
error: could not compile `worldviewer`

This is a cute little problem. I have a windowed program with a "winit" event loop. Winit event loops, which are closures, never return; they just exit, So any cleanup must take place withing the event loop when handing the exit event. The event loop starts like this: |event, _window_target, control| match event {

I also have another thread running, launched at "let loaderthread = start_content_thread()". "loaderthread", a JoinHandle, is created outside the event loop, and then captured by the event loop closure. Inside the event loop closure, I tell the thread to quit via a flag, and then do a join, waiting for it to finish, clean up, and exit.

Can't do that. See error above. Normally, that would just be a move of JoinHandle into the closure, but that doesn't work here.

Can't copy a JoinHandle.

Boxing loaderthread yields:

error[E0507]: cannot move out of `*loaderthread`, as `loaderthread` is a captured variable in an `FnMut` closure
   --> src/
279 |     let loaderthread = Box::new(start_content_thread(
    |         ------------ captured outer variable
471 |             loaderthread.join();                                // wait for loader thread
    |             ^^^^^^^^^^^^ move occurs because `*loaderthread` has type `std::thread::JoinHandle<()>`, which does not implement the `Copy` trait

Any ideas?

Still somewhat stuck on how to structure startup and shutdown around an event loop.

  • hijacks the main thread and never returns. Anything not passed to the event loop closure will never be dropped.

  • Some types, notably JoinHandle, can't be passed to the event loop closure, because the closure is invoked for each event. That prevents a move, because the move would happen more than once. So there's no way to get an un-copyable type into the closure. I think.

This has to be a common problem, since it applies to all GUI programs, so I'm probably missing something.

Put it in an Option and use Option::take to take ownership of it.

1 Like

Ah! That works. Thanks.

Not clear why, though. No combination of Box or Arc or Rc seemed to work; those all violated the FnMut rule. Why doesn't Option?

Because option provides an answer to "what happens if you run the closure twice?".

Ah! That makes sense.

For completeness, an alternative solution (not really): you can put a JoinHandle into an object with a Drop which does join() in drop, and move that into the closure. To implement such an RAII guard though, you’ll need an Option or a ManuallyDrop + unsafe.

Because Option::take() does something that is usually not possible: moving out of a &mut reference by-value. It can work exactly because it's an Option, and Some(T) can be replaced by a None (since you can make up the None variant out of thin air), and then the T that was previously owned by the Option can now be returned to the caller by-value.

You can't do this with Arc or Box because there's nothing to fill the moved-from place with. Well, if your value type is Default, then you can use mem::take() which is similar to Option::take(), only it replaces the moved-from value with the result of a call to Default::default().

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.