rustclosuresownership

Why does .clone() not prevent a move when used in a closure?


I'm attempting to optimize an application through smart cloning and borrowing, and I'm observing the following behavior. The program below wouldn't work:

fn f( string: String) {
    println!("{}", string );
}

fn main() {
    let my_string: String = "ABCDE".to_string();
    f( my_string );
    f( my_string );
}

It generates the well-known "used after move" error.

7 |     f( my_string );
  |        --------- value moved here
8 |     f( my_string );
  |        ^^^^^^^^^ value used here after move

This can be solved by cloning my_string. The program below works fine:

fn f( string: String) {
    println!("{}", string );
}

fn main() {
    let my_string: String = "ABCDE".to_string();
    f( my_string.clone() );
    f( my_string.clone() );
}

However, if you use the same approach in a multi-threaded environment, cloning doesn't help any longer. When the function calls are embedded in threads:

use std::thread;

fn f( string: String) {
    println!("{}", string );
}

fn main() {
    let my_string: String = "ABCDE".to_string();
    thread::spawn( move || { f( my_string.clone() ); } );
    thread::spawn( move || { f( my_string.clone() ); } );
}

the program generates the "used after move" error again:

10 |     thread::spawn( move || { f( my_string.clone() ); } );
   |                    ^^^^^^^      --------- use occurs due to use in closure
   |                    |
   |                    value used here after move

However, you can remedy this by moving the thread into the function, with the same net effect:

use std::thread;

fn f( string: String) {
    thread::spawn( move || { println!("{}", string ); } );
}

fn main() {
    let my_string: String = "ABCDE".to_string();
    f( my_string.clone() );
    f( my_string.clone() );
}

The above program works fine. Or, if you prefer, you can clone my_string in advance and use the clone in the second function call:

use std::thread;

fn f( string: String) {
    println!("{}", string );
}

fn main() {
    let my_string: String = "ABCDE".to_string();
    let my_second_string: String = my_string.clone();

    thread::spawn( move || { f( my_string.clone() ); } );
    thread::spawn( move || { f( my_second_string ); } );
}

It looks a little like trial and error, but some theory can perhaps explain it.

There is another question regarding the "used after move" error. The other question discusses the effect of to_string(), while this one discusses clone() in a threaded environment.


Solution

  • However, if you use the same approach in a multi-threaded environment, cloning doesn't help any longer. [...] It looks a little like trial and error, but some theory can perhaps explain it.

    It does help if you do it right. The problem here is that a move closure means the value gets moved into the closure before the closure runs1.

    So when you write

        thread::spawn(move || { f( my_string.clone()); });
    

    what happens is

    1. create the closure, move my_string inside the closure
    2. spawn the thread
    3. clone my_string
    4. call f with the clone

    By the time you're cloning my_string it's already way too late, because it's been moved from the outer function to the inside the closure and thread. It's as if you'd try to fix the original snippet by changing the contents of f thus:

    fn f(string: String) {
        println!("{}", string.clone());
    }
    
    fn main() {
        let my_string = "ABCDE".to_string();
        f(my_string);
        f(my_string);
    }
    

    That obviously doesn't fix anything.

    The usual solution for this is the so-called "precise capture pattern". "Capture clause" comes from C++ where it's a native feature, but in Rust it's not. What you do is that instead of creating a closure directly, you create and return the closure from a block, and before creating and returning the closure you can create a bunch of bindings which then get moved into the closure. It essentially provides a "closure setup" which is isolated:

    thread::spawn({
        // shadow the outer `my_string` with a clone of itself
        let my_string = my_string.clone();
        // then move the clone into the closure
        move || { f(my_string); }
    });
    

    Incidentally an other option if you don't need to modify the String is to just put it in an Arc. Though you still need to clone outside the closure and move the arc inside the closure. The advantage is that cloning an arc just increments its refcount, it's atomic so it's not free, but it can be cheaper than cloning a complex / expensive object.


    [1]: technically that can also happen with non-move closures, more precisely move closure will move (/ copy) everything it refers to, while a non-move closure may move or just borrow depending how the item is being used.