Reputation: 2925
In How does Rust prevent data races when the owner of a value can read it while another thread changes it?, I understand I need &mut self
, when we want to mutate an object, even when the method is called with an owned value.
But how about primitive values, like i32
? I ran this code:
fn change_aaa(bbb: &mut i32) {
*bbb = 3;
}
fn main() {
let mut aaa: i32 = 1;
change_aaa(&mut aaa); // somehow run this asynchronously
aaa = 2; // ... and will have data race here
}
My questions are:
Two or more pointers access the same data at the same time.
At least one of the pointers is being used to write to the data.
There’s no mechanism being used to synchronize access to the data.
change_aaa(&mut aaa)
into a thread, according to Why can't std::thread::spawn accept arguments in Rust? and How does Rust prevent data races when the owner of a value can read it while another thread changes it?. However, is it designed to be hard or impossible to do this, or just because I am unfamiliar with Rust?Upvotes: 2
Views: 741
Reputation: 154876
The signature of change_aaa
doesn't allow it to move the reference into another thread. For example, you might imagine a change_aaa()
implemented like this:
fn change_aaa(bbb: &mut i32) {
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(1));
*bbb = 100; // ha ha ha - kaboom!
});
}
But the above doesn't compile. This is because, after desugaring the lifetime elision, the full signature of change_aaa()
is:
fn change_aaa<'a>(bbb: &'a mut i32)
The lifetime annotation means that change_aaa
must support references of any lifetime 'a
chosen by the caller, even a very short one, such as one that invalidates the reference as soon as change_aaa()
returns. And this is exactly how change_aaa()
is called from main()
, which can be desugared to:
let mut aaa: i32 = 1;
{
let aaa_ref = &mut aaa;
change_aaa(aaa_ref);
// aaa_ref goes out of scope here, and we're free to mutate
// aaa as we please
}
aaa = 2; // ... and will have data race here
So the lifetime of the reference is short, and ends just before the assignment to aaa
. On the other hand, thread::spawn()
requires a function bound with 'static
lifetime. That means that the closure passed to thread::spawn()
must either only contain owned data, or references to 'static
data (data guaranteed to last until the end of the program). Since change_aaa()
accepts bbb
with with lifetime shorter than 'static
, it cannot pass bbb
to thread::spawn()
.
To get a grip on this you can try to come up with imaginative ways to write change_aaa()
so that it writes to *bbb
in a thread. If you succeed in doing so, you will have found a bug in rustc. In other words:
However, is it designed to be hard or impossible to do this, or just because I am unfamiliar with Rust?
It is designed to be impossible to do this, except through types that are explicitly designed to make it safe (e.g. Arc
to prolong the lifetime, and Mutex
to make writes data-race-safe).
Upvotes: 4
Reputation: 42217
Is this safe in a non concurrent situation? According to this post, if we think owned value if self as a pointer, it is not safe according the following rules, however, it compiles.
Two or more pointers access the same data at the same time. At least one of the pointers is being used to write to the data. There’s no mechanism being used to synchronize access to the data.
It is safe according to those rules: there is one pointer accessing data at line 2 (the pointer passed to change_aaa
), then that pointer is deleted and another pointer is used to update the local.
Is this safe in a concurrent situation? I tried, but I find it hard to put
change_aaa(&mut aaa)
into a thread, according to post and post. However, is it designed to be hard or impossible to do this, or just because I am unfamiliar with Rust?
While it is possible to put change_aaa(&mut aaa)
in a separate thread using scoped threads, the corresponding lifetimes will ensure the compiler rejects any code trying to modify aaa
while that thread runs. You will essentially have this failure:
fn main(){
let mut aaa: i32 = 1;
let r = &mut aaa;
aaa = 2;
println!("{}", r);
}
error[E0506]: cannot assign to `aaa` because it is borrowed
--> src/main.rs:10:5
|
9 | let r = &mut aaa;
| -------- borrow of `aaa` occurs here
10 | aaa = 2;
| ^^^^^^^ assignment to borrowed `aaa` occurs here
11 | println!("{}", r);
| - borrow later used here
Upvotes: 1