1

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.

1
  • It has become more or less idiomatic to write the clone/move idiom this way: thread::spawn({ let my_string = my_string.clone(); move || { f( my_string ); } });. Commented Dec 15, 2022 at 21:05

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

Comments

3

The move keyword means that Rust now has to move all used variables from outside into the closure. So by using my_string inside of move || {} you moved it in. If you just want to use a reference inside of the closure you can borrow outside the closure:

fn f(string: String) { println!("{}", string); } fn main() { let my_string: String = "ABCDE".to_string(); { // Take the reference outside of the `move` // closures so only the reference but not the // actual string gets moved inside. // // Since shared references are `Copy` they get // copied instead and you can reuse them. let my_string: &String = &my_string; let a = move || { f(my_string.clone()); }; let b = move || { f(my_string.clone()); }; } } 

But because you want to use it in a different thread wihch might outlive the current function the reference would have to be 'static.

So to avoid moving my_string into the closure which gets passed to another thread you have to do the clone outside of the closure.

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

2 Comments

The first code snippet is irrelevant to this issue. You are using a &str as an argument and &str is not subject to moving. You can have many move || f(my_string) without cloning and/or many f(my_string) without cloning, and you never get the "used after move" error. In this case, using clone() is pointless.
That's the point of the first snippet. You take the reference so you only move (or more specifically copy) references inside the move closures. Btw there are no &str anywhere. The type of my_string inside of the block is &String similar but slightly different from &str. I've edited the answer to make it more clear. @KalleSvensson

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.