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.
EDIT : 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.
EDIT2 : After reading the answers, the conclusion is as follows: In the function call f(my_string.clone())
cloning is done first and moving the cloned variable into the function later, so my_string
is never moved. In the closure move || { f(my_string.clone())
move || { f(my_string.clone())
moving of my_string
into the closure is done first , so cloning my_string
cannot solve it. It is a difference between a function and a closure. Thread
has nothing to do with this except that it imposes move
in the closure.
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 || {}
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);
});
}
}
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 runs 1 .
So when you write
thread::spawn(move || { f( my_string.clone()); });
what happens is
my_string
inside the closuremy_string
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.