先看一段代码
#[test]
fn test_hash_map() {
let mut mp: HashMap<&str, &str> = HashMap::new();
mp.insert("k", "v");
mp.insert("k1", "v1");
let x = mp.keys().clone();
for k in x {
mp.insert("k2", "v1");
mp.insert("k3", "v1");
}
mp.insert("k2", "v1");
mp.insert("k3", "v1");
}
看,它报错
error[E0502]: cannot borrow `mp` as mutable because it is also borrowed as immutable
--> src/process.rs:153:9
|
151 | let x = mp.keys().clone();
| -- immutable borrow occurs here
152 | for k in x {
| - immutable borrow later used here
153 | mp.insert("k2", "v1");
| ^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
报错告诉我们,不允许将mp
作为mutable,因为它已经用于immutable了。难道,HashMap插入数据完毕,开始读取数据之后,不能再次插入数据了?我一开始这么怀疑,不应该啊,于是代码改成这样
#[test]
fn test_hash_map() {
let mut mp: HashMap<&str, &str> = HashMap::new();
mp.insert("k", "v");
mp.insert("k1", "v1");
mp.keys().clone();
mp.insert("k2", "v1");
mp.insert("k3", "v1");
}
正常了,没报错了。看样子问题出在这个for ..in..
中。仔细分析代码,我在for中遍历了它的key,或者说我正在将这个HashMap中的数据拿出来,这时候,我在for中尝试往这个HashMap中写入数据,写入数据会让这个HashMap发生变更,这里第一感让我觉得可能有问题。比如我HashMap中有10个元素,我在遍历它,然后在for中间插入新元素,那么是不是有下列问题:1,我的HashMap会不会越遍历越多,会不会永远无法遍历完;2,HashMap是无序的,我将新元素插入HashMap中,会不会导致我已经遍历过的数据由于插入新数据,导致再次被读取出来,因为它可能位置发生变化了嘛。
其实这就是数据竞争和它带来的不确定性问题,rust作为一个内存安全第一的编程语言,编译器会教你做人。
于是,我再改
#[test]
fn test_hash_map() {
let mut mp: HashMap<&str, &str> = HashMap::new();
mp.insert("k", "v");
mp.insert("k1", "v1");
let x: Vec<&str> = mp.keys().map(|k| *k).collect();
for k in x {
mp.insert("k2", "v1");
mp.insert("k3", "v1");
}
mp.insert("k2", "v1");
mp.insert("k3", "v1");
}
这回正常了。可以看到我的操作let x: Vec<&str> = mp.keys().map(|k| *k).collect();
是将keys()拿到的Keys
迭代器(仍旧从前面的HashMap里迭代)通过.collect()
方法将迭代器里的元素“倒”入到Vec<&str>
,这样这个Vec
就是一个独立与HashMap内存空间之外的变量,再基于这个Vec进行遍历,就可以避免“边遍历边修改的”的情况了。
那么大家可以思考一下,其他语言,比如golang,遇到这种情况是怎么处理的呢。