How do intertwined scopes create a "data race"? [duplicate]
Asked Answered
O

1

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.

Ober answered 17/5, 2020 at 12:38 Comment(5)
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. play.rust-lang.org/… also, your code is unclear what should be the first value, 2 or 3 ?Halfcaste
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.Ober
This gives some description specific to the single threaded case: manishearth.github.io/blog/2015/05/17/…Lymn
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.Lymn
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.Ballista
G
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

Glanti answered 17/5, 2020 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.