1

The Rust book talks about having multiple readers and multiple mutable references to an object as a data race situation that may lead to issues.

For example, this code:

fn main() {
    let mut x = 1;
    let r1 = &mut x;
    *r1 = 2;
    let r2 = &mut x;
    *r2 = 3;
    println!("{}", r1);
    println!("{}", r2);
}

will be rejected by Rust compiler because r1 and r2 scopes are intertwined.

But what is problem here? I mean, this is just one thread and there is no "reading and writing at the same time", so all these statements should be executed strictly sequentially and give deterministic reproducible result.

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Student4K
  • 916
  • 2
  • 9
  • 20
  • because Rust say it ? the rule are clear one mutable borrow at the same time, you are using r1 after r2 creation so compile will stop you, period. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=01e730fe5abec57419814998a17967f2 also, your code is unclear what should be the first value, 2 or 3 ? – Stargateur May 17 '20 at 13:10
  • Well, since both ```r1``` and ```r2``` reference the same object, it would be meaningful to conclude that after the last change of that object both ```r1``` and ```r2``` should reference x = 3. – Student4K May 17 '20 at 13:28
  • 1
    This gives some description specific to the single threaded case: https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/ – chub500 May 17 '20 at 13:30
  • I _think_ in your case a sufficiently intelligent compiler that wasn't worried about anti-patterns could safely allow your code. I say sufficiently intelligent because r1 has to know r2 didn't do anything to x to invalidate the reference. At scale that might become cumbersome? Just a guess. – chub500 May 17 '20 at 13:37
  • 2
    If you pass the rust type check your program has no races (or leaks). The converse is definitely not true, and that's what you're observing here. – Paul Hankin May 17 '20 at 13:39

1 Answers1

5

From Niko Matsakis' blog:

I’ve often thought that while data-races in a technical sense can only occur in a parallel system, problems that feel a lot like data races crop up all the time in sequential systems. One example would be what C++ folk call iterator invalidation—basically, if you are iterating over a hashtable and you try to modify the hashtable during that iteration, you get undefined behavior. Sometimes your iteration skips keys or values, sometimes it shows you the new key, sometimes it doesn’t, etc.

But whatever the outcome, iterator invalidation feels very similar to a data race. The problem often arises because you have one piece of code iterating over a hashtable and then calling a subroutine defined over in some other module. This other module then writes to the same hashtable. Both modules look fine on their own, it’s only the combination of the two that causes the issue. And because of the undefined nature of the result, it often happens that the code works fine for a long time—until it doesn’t.

Rust’s type system prevents iterator invalidation.

Rust's type system disallows single-threaded programs like the one below to compile because they would result in Undefined Behavior and while that technically isn't a data race this particular error is in the same ballpark of "errors caused by two independent pieces of code mutating the same data in an interweaved fashion" so it's very similar to a data race and I believe that's what the Rust book was trying to communicate:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 1);
    map.insert(2, 2);
    map.insert(3, 3);
    
    for _ in map.iter() {
        map.insert(4, 4); // compile error!
    }
}

playground

Community
  • 1
  • 1
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98