2

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:

  1. Is this safe in a non concurrent situation? According to The Rust Programming Language, if we think of the owned value 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.

  2. 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 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?
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
calvin
  • 2,125
  • 2
  • 21
  • 38
  • 4
    It's actually quite easy to move it to a different thread, but it's also safe, because if you run `change_aaa` in another thread, `aaa` will implicitly be copied, because `i32` implements the [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) trait. If you were to use something that doesn't you'd get a compile error akin to `value borrowed here after move`. [Demonstration](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9fac4d3f3f4aa9d1f7eadf2a3229df10) – MindSwipe Nov 23 '21 at 14:56
  • 1
    [Non-lexical lifetimes](https://stackoverflow.com/questions/50251487/what-are-non-lexical-lifetimes) allow you to borrow mutably several times in the same scope, as long as these borrows do not overlap. In your example, when you execute `aaa = 2;`, `change_aaa()` has already ended and there is no data race. – justinas Nov 23 '21 at 15:01
  • 1
    The book shows how to create a situation with concurrency on [chapter 16](https://doc.rust-lang.org/book/ch16-03-shared-state.html). But concurrency is not necessary to trigger unsafety here ([example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0c9ba87d0717313ca47d984fc6e3511f)). – E_net4 Nov 23 '21 at 15:14

2 Answers2

4

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).

user4815162342
  • 141,790
  • 18
  • 296
  • 355
1

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
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Masklinn
  • 34,759
  • 3
  • 38
  • 57