相互交织的范围如何创建 "data race"?

How do intertwined scopes create a "data race"?

Rust book 讨论了拥有多个读取器和对一个对象的多个可变引用作为可能导致问题的数据竞争情况。

例如,这段代码:

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

将被 Rust 编译器拒绝,因为 r1r2 范围交织在一起。

但是这里有什么问题呢?我的意思是,这只是一个线程,没有 "reading and writing at the same time",因此所有这些语句都应严格按顺序执行并给出确定的可重现结果。

来自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 的类型系统不允许像下面这样的单线程程序进行编译,因为它们会导致未定义的行为,虽然技术上不是数据竞争这个特殊的错误是在“由两个独立的代码片段以交织的方式改变相同数据引起的错误”的同一个范围内,因此它与数据竞赛非常相似,我相信这就是 Rust 书试图传达的内容:

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