Reputation: 43
I am a complete newbie in Rust and have just started getting into its ownership system (4-th chapter of the book). Compiler handles a lot of errors that may occur with values and references, but lets imagine situation below:
Let's assume there is code like this, but change_string
opens a new thread and perform mutations there.
fn main() {
let mut s = String::from("Hello");
// perform in thread, so flow will not be synchronous
change_string(&mut s);
s.push_str("..........");
println!("{}", s);
}
fn change_string(some_string: &mut String) {
some_string.push_str(", World");
}
Currently I receive Hello, World..........
, but what will happen if adding of , World
will be in the separate thread?
Upvotes: 2
Views: 880
Reputation: 98516
Rust will not let you do unsafe things (without using the unsafe
keyword), that includes anything related with threads. Why not just try and see how it fails to compile?
fn change_string(some_string: &mut String) {
std::thread::spawn(move || {
some_string.push_str(", World");
});
}
It produces this error:
error[E0621]: explicit lifetime required in the type of `some_string`
--> src/main.rs:14:5
|
13 | fn change_string_1(some_string: &mut String) {
| ----------- help: add explicit lifetime `'static` to the type of `some_string`: `&'static mut std::string::String`
14 | std::thread::spawn(move || {
| ^^^^^^^^^^^^^^^^^^ lifetime `'static` required
This means that you cannot pass a reference with a non-static lifetime to a thread, because the thread could live longer than such reference, and that would be unsafe.
You were probably thinking about issues with data races, but those are actually not the problem. The real problem is that your String
may live as long as the thread. Actually, if you use scoped threads, from the crossbeam
crate, it will just work:
fn change_string(some_string: &mut String) {
crossbeam::scope(|s| {
s.spawn(|_| {
some_string.push_str(", World");
});
}).unwrap();
}
Now it works because the crossbeam::scope()
call will not return until all the threads spawned inside are completed. And thus the lifetime of your String
is always strictly longer than the thread and everything just works.
But what about data races? There are none, because &mut
references in Rust are unique. This means that you cannot have two of them pointing to the same object, so it doesn't matter from what thread you are changing your object (as long as your object implements Sync
but most do), you are doing it from just one, and there are no races.
If you try to create two threads to modify the object, even with crossbeam
:
fn change_string(some_string: &mut String) {
crossbeam::scope(|s| {
s.spawn(|_| {
some_string.push_str(", World");
});
s.spawn(|_| {
some_string.push_str(", World");
});
}).unwrap();
}
You get this error message:
error[E0524]: two closures require unique access to `some_string` at the same time
Remember that &mut
means unique access in addition to ability to mutate.
And if you try to move the reference into the closures instead of borrowing it:
fn change_string(some_string: &mut String) {
crossbeam::scope(|s| {
s.spawn(move |_| {
some_string.push_str(", World");
});
s.spawn(move |_| {
some_string.push_str(", World");
});
}).unwrap();
}
You get this one in the second closure:
error[E0382]: use of moved value: `some_string`
because the reference was already moved into the first one and cannot be used again.
Of course you can spawn a thread from the thread and that will work:
fn change_string(some_string: &mut String) {
crossbeam::scope(|s| {
s.spawn(|s| {
some_string.push_str(", World");
s.spawn(|_| {
some_string.push_str(", World");
});
});
}).unwrap();
}
But notice how it is still totally race free, because once the second thread is created the first one loses access to the unique mutable reference.
Upvotes: 4