Getting a difficult to understand borrow checker error when writing a closure

I'm trying to write a function that starts a job in background and returns a callback that will terminate this task. This is the implementation:

// Spawn a task in a new thread
// The task will be given a Receiver<()>
// The task must perform cleanup and shutdown when data is sent on that receiver
// Calling the closure returned by this function will send shutdown notification to the task and
// block until the task is finished
pub fn spawn_task(task: fn(flume::Receiver<()>)) -> Box<dyn Fn()> {
    let (ctrl_tx, ctrl_rx) = flume::bounded::<()>(1);
    let thread = std::thread::spawn(move || task(ctrl_rx));
    Box::new(move || {
        let _ = ctrl_tx.send(());
        let _ = thread.join();
    })
}

The compilation fails with an error saying "cannot move out of thread, a captured variable in an Fn closure".

It compiles if I rewrite the function to explicitly return an object with the context needed for the callback to work:

pub struct AsyncTaskContext {
    thread: JoinHandle<()>,
    ctrl_tx: Sender<()>,
}
impl AsyncTaskContext {
    pub fn join(self) {
        let _ = self.ctrl_tx.send(());
        let _ = self.thread.join();
    }
}

pub fn spawn_task_with_type(task: fn(flume::Receiver<()>)) -> AsyncTaskContext {
    let (ctrl_tx, ctrl_rx) = flume::bounded::<()>(1);
    let thread = std::thread::spawn(move || task(ctrl_rx));
    AsyncTaskContext { thread, ctrl_tx }
}

Why doesn't the first implementation work as expected?

the return type dyn Fn() only get a shared reference to the captured context, so you cannot call thread.join() which requires thread be movable. use dyn FnOnce() should fix it.

5 Likes