33

I'm trying to figure out how to send a function through a channel, and how to avoid extra cloning in order to execute the function at the other end. If I remove the extra cloning operation inside the closure, I get the following error:

error: cannot move out of captured outer variable in an 'Fn' closure

Ignoring the fact that this code does absolutely nothing, and makes use of a global mutable static Sender<T>, it represents what I'm trying to achieve while giving the proper compiler errors. This code is not meant to be ran, just compiled.

use std::ops::DerefMut;
use std::sync::{Arc, Mutex};
use std::collections::LinkedList;
use std::sync::mpsc::{Sender, Receiver};

type SafeList = Arc<Mutex<LinkedList<u8>>>;
type SendableFn = Arc<Mutex<(Fn() + Send + Sync + 'static)>>;
static mut tx: *mut Sender<SendableFn> = 0 as *mut Sender<SendableFn>;

fn main() {
    let list: SafeList = Arc::new(Mutex::new(LinkedList::new()));
    loop {
        let t_list = list.clone();
        run(move || {
            foo(t_list.clone());
        });
    }
}

fn run<T: Fn() + Send + Sync + 'static>(task: T) {
    unsafe {
        let _ = (*tx).send(Arc::new(Mutex::new(task)));
    }
}

#[allow(dead_code)]
fn execute(rx: Receiver<SendableFn>) {
    for t in rx.iter() {
        let mut guard = t.lock().unwrap();
        let task = guard.deref_mut();
        task();
    }
}

#[allow(unused_variables)]
fn foo(list: SafeList) { }

Is there a better method to getting around that error and/or another way I should be sending functions through channels?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nathansizemore
  • 3,028
  • 7
  • 39
  • 63

1 Answers1

42

The problem with Fn() is that you can call it multiple times. If you moved out of a captured value, that value would not be available anymore at the next call. You need a FnOnce() to make sure calling the closure also moves out of it, so it's gone and can't be called again.

There's no way to have an Arc<Mutex<(FnOnce() + Send + Sync + 'static)>>. This would again require that you statically guarantee that after you call the function, noone else can call it again. Which you cannot, since someone else might have another Arc pointing to your FnOnce. What you can do is box it and send it as Box<FnOnce() + Send + Sync + 'static>. There's only ever one owner of a Box.

The trouble with FnOnce() is, is that you can't really call it while it's in the Box, because that would require moving it out of the Box and calling it. But we don't know the size of it, so we cannot move it out of the Box. In the future Box<FnOnce()> closures might become directly usable.

"Luckily" this problem occurred more often, so there's FnBox. Sadly this requires nightly to work. Also I couldn't figure out how to use the function call syntax that is described in the docs, but you can manually call call_box on the Box<FnBox()>. Try it out in the Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
oli_obk
  • 28,729
  • 6
  • 82
  • 98
  • Unfortunately, stable is my only option. It looks like you just confirmed what I've already tried and figured out, but someone else may find this super helpful who has the ability to use nightly. Thanks :) – nathansizemore Nov 12 '15 at 14:02
  • 3
    You can use a hack. Still use a `Fn` and only move in `Option`s that you move out of with `take`. Then you get a runtime-error when you mis-use your `Fn` – oli_obk Nov 12 '15 at 14:38
  • What if I need a `Fn` though, because of a library requirement. – Jeroen Nov 13 '17 at 15:07
  • 2
    @JeroenBollen the hack still applies. `move || (foo_option.take().unwrap())()` – oli_obk Nov 14 '17 at 13:23