How can I move a closure into a closure within a closure?


#1

Well, it seems it is a confusing title but it is a problem I have to solve.

The scenario is that a socket-listener process each connections, and each connections has multiple messages to be processed. User will define message-processed logic into a closure.
So I want to pass a closure into a closure within a closure.

I minimized the scenario like this:

use std::thread;

fn main() {
    let mut count = 0;
    test(move || {
        count += 1;
        println!("Count: {:?}",count);
    })
}

fn test<F>(mut function: F)
    where F: FnMut() + Send + Sync + 'static
{
    let things = vec![1, 2, 3, 4];
    let _ = things.iter().for_each(|&d| {
        println!("{:?}", d);
        thread::spawn(move || {
            function()
        });
    });
}

And the error information as bellow:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> src/main.rs:17:23
   |
11 | fn test<F>(mut function: F)
   |            ------------ captured outer variable
...
17 |         thread::spawn(move || {
   |                       ^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

error: aborting due to previous error

Anyone has idea?


#2

Well, you’re trying to move function into the closure, but you’re calling it repeatedly (4 times, that is). That won’t work with moving, it’s gone after that! You can “solve” this problem like this, but that surfaces another problem: You’re spawning threads, which are not guaranteed to finish soon enough, so count might go out of scope (same for the function reference of course). If you restrict yourself to just call a closure (maybe like this, although that’s pretty contrieved for the sake of the example), it all works out.


#3

You may be able to take advantage of closures being Clone when their captured (or moved) state is. Here is an example using your stripped down version. There’s an example of each thread having its own counter and another example where the counter is shared using an AtomicUsize (should be a different Atomic type but eg AtomicU64 is unstable at the moment).


#4

Well, it seems you are trying to use an FnMut as if it were FnOnce. The first solution that comes in mind is that you use Clone on the closure. But then the inner variable will not be shared anymore. This kind of ruins your idea. So, the solution is to wrap the variable with an Arc.

I wrote an example using Arc<AtomicUsize>. If your type does not fit to an usize or a pointer, you may use something like Mutex.

https://play.rust-lang.org/?gist=a5d8ac7b1e2adfefa10648dd9b733266&version=stable&mode=debug&edition=2015


#5

Do you see the problem? You were trying to share mutable resources between threads. And thanks god Rust prevented you from doing that!