First try, define a mutable vector then pass a closure to thread, inside closure, push another value to vector.
use std::{thread, time};
fn main() {
let mut numbers = vec![1, 2, 3];
let t1 = thread::spawn(|| {
thread::sleep(time::Duration::from_secs(1));
numbers.push(4);
println!("numbers: {:?}", numbers);
});
t1.join().unwrap();
println!("numbers: {:?}", numbers);
}
If we run the code, we got errors:
error[E0373]: closure may outlive the current function, but it borrows `numbers`, which is owned by the current function
--> thread1.rs:5:28
|
5 | let t1 = thread::spawn(|| {
| ^^ may outlive borrowed value `numbers`
6 | thread::sleep(time::Duration::from_secs(1));
7 | numbers.push(4);
| ------- `numbers` is borrowed here
...
5 | let t1 = thread::spawn(move || {
| ^^^^^^^
error[E0502]: cannot borrow `numbers` as immutable because it is also borrowed as mutable
...
10 | t1.join().unwrap();
11 | println!("numbers: {:?}", numbers);
| ^^^^^^^ immutable borrow occurs here
We got 2 errors here: the first one "closure may outlive the current function", this is because when we operate the numbers
inside closure in a thread, the main thread might drop numbers
, then the thread closure will refer to an invalid memory address. To solve this problem, we need to move the ownership of numbers
to closure, such that Rust will know only the thread closure will use the numbers
. Let's change the code:
...
let t1 = thread::spawn(move || {
...
Let's compile the code again, but still we have an error:
error[E0382]: borrow of moved value: `numbers`
--> thread1.rs:11:31
|
4 | let mut numbers = vec![1, 2, 3];
| ----------- move occurs because `numbers` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
5 | let t1 = thread::spawn(move || {
| ------- value moved into closure here
6 | thread::sleep(time::Duration::from_secs(1));
7 | numbers.push(4);
| ------- variable moved due to use in closure
...
11 | println!("numbers: {:?}", numbers);
| ^^^^^^^ value borrowed here after move
Because we move the ownership of numbers
to thread closure, the outside scope doesn't have the ownership of numbers
, it cannot use the numbers
anymore. Let's remove that line:
use std::{thread, time};
fn main() {
let mut numbers = vec![1, 2, 3];
let t1 = thread::spawn(move || {
thread::sleep(time::Duration::from_secs(1));
numbers.push(4);
println!("numbers: {:?}", numbers);
});
t1.join().unwrap();
}
This time it can run:
numbers: [1, 2, 3, 4]
Let's try another example:
use std::{thread, time};
fn main() {
let duration = time::Duration::from_secs(1);
let t1 = thread::spawn(|| {
thread::sleep(duration);
println!("hello from spawned thread");
});
t1.join().unwrap();
thread::sleep(duration);
println!("hello from main thread");
}
The code has a similar logic like the first one, but inside the thread closure, instead of using vector, we used the Duration instance.
Compile it, we got an error:
error[E0373]: closure may outlive the current function, but it borrows `duration`, which is owned by the current function
--> thread2.rs:5:28
|
5 | let t1 = thread::spawn(|| {
| ^^ may outlive borrowed value `duration`
6 | thread::sleep(duration);
| -------- `duration` is borrowed here
|
That is as expected, the closure using the duration in the outer scope, Rust suggested us to use the move
keyword. Let's add it:
use std::{thread, time};
fn main() {
let duration = time::Duration::from_secs(1);
let t1 = thread::spawn(move || {
thread::sleep(duration);
println!("hello from spawned thread");
});
t1.join().unwrap();
thread::sleep(duration);
println!("hello from main thread");
}
Let's re-run the code:
hello from spawned thread
hello from main thread
It works! But wait, we are using duration
not only in the thread closure, but also in the outer scope, why don't we have the error like in the first example? Remember in the first example, we are not allowed to use the numbers
in the outer scope after we move the ownership to the thread closure. But in this example, we don't have such error and the code runs.
The reason is, for the Duration
type, move
won't really move the ownership, instead it will copy that variable to closure, such that both the thread closure and outer scope can use that variable.
So what move
will do is:
- First check if this variable implemented the
Copy
trait, if the variable does, thenmove
will do a variable copy and move the copy to the closure. In this case, the closure and outer scope can both use this variable. - If this variable doesn't implement the
Copy
trait,move
will move the ownership of that variable to thread closure. In this case, the outer scope cannot use the variable anymore.
To confirm this, if we dig into the source code of Duration type[1], we can see:
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Duration {
secs: u64,
nanos: u32, // Always 0 <= nanos < NANOS_PER_SEC
}
The magic happens in the code #[derive(Clone, Copy)]
. We mentioned the Copy
trait above, but what's this Clone? Well, Clone
is also required, as it's a supertrait of Copy
.[2]
Let's see another example, this time we are going to use our own struct.
use std::{thread, time};
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let duration = time::Duration::from_secs(1);
let p = Point {x: 1, y:2};
let t1 = thread::spawn(move || {
thread::sleep(duration);
println!("[Spawned thead] Point {:?}", p);
});
t1.join().unwrap();
thread::sleep(duration);
println!("[Main thead] Point {:?}", p);
}
We defined a struct called "Point" and created an instance p
, then in thread closure, we used move
and print out p
, in the main thread, we also printed p
.
Compile it we got the error:
error[E0382]: borrow of moved value: `p`
--> thread3.rs:18:41
|
11 | let p = Point {x: 1, y:2};
| - move occurs because `p` has type `Point`, which does not implement the `Copy` trait
12 | let t1 = thread::spawn(move || {
| ------- value moved into closure here
13 | thread::sleep(duration);
14 | println!("[Spawned thead] Point {:?}", p);
| - variable moved due to use in closure
...
18 | println!("[Main thead] Point {:?}", p);
| ^ value borrowed here after move
Review the move
rule we wrote above: since Point
doesn't implement the Copy
trait, so move
will move the ownership of p
, then the outer scope cannot use the p
anymore.
There are 2 ways to implement the Copy
trait to our struct [3], the easiest way if to use the derive
, just like the one in the Duration
source code. So change #[derive(Debug)]
to #[derive(Debug, Clone, Copy)]
. Here is the complete code:
use std::{thread, time};
#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let duration = time::Duration::from_secs(1);
let p = Point {x: 1, y:2};
let t1 = thread::spawn(move || {
thread::sleep(duration);
println!("[Spawned thead] Point {:?}", p);
});
t1.join().unwrap();
thread::sleep(duration);
println!("[Main thead] Point {:?}", p);
}
Compile and run the code, it works!
[Spawned thead] Point Point { x: 1, y: 2 }
[Main thead] Point Point { x: 1, y: 2 }
Top comments (0)