Rust中HashMap不可变借用时能否可变借用其他条目?
Rust HashMap迭代时修改其他条目:逻辑安全却过不了编译?
你想遍历HashMap,根据每个条目的内容修改另一个完全不同的条目,逻辑上绝对安全,但Rust编译器就是不认可。
先看你的代码和报错:
type Hash = String; type Hashes = Vec<Hash>; struct RawNode { parents : Hashes, children : Hashes, // 其他字段... } fn make_bi() -> HashMap<Hash, RawNode> { let raw_graph = make_raw(); // 从标准输入生成父提交映射,要填充children字段 for (h, n) in raw_graph.iter() { for ph in n.parents.iter() { if let Some(p) = raw_graph.get_mut(ph) { p.children.push(h.to_string()); } } } raw_graph }
报错信息:
cannot borrow `raw_graph` as mutable because it is also borrowed as immutable | 77 | for (h, n) in raw_graph.iter() { | ---------------- | | | immutable borrow occurs here | immutable borrow later used here 78 | for ph in n.parents.iter() { 79 | if let Some(p) = raw_graph.get_mut(ph) { | ^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
你误解的地方
你以为iter()只会锁定当前迭代的条目,但实际上,HashMap::iter()会获取整个HashMap的不可变借用——只要这个迭代器还在运行(也就是for循环没结束),整个容器就处于不可变借用状态。而get_mut()需要的是整个HashMap的可变借用,Rust的借用规则不允许同一时间存在可变和不可变的容器级借用,哪怕你逻辑上不会碰当前迭代的条目,编译器也没法静态验证这一点。
不用冗余克隆的解决办法
最直接高效的方式是把“收集需要修改的关系”和“实际修改”分成两步:
- 先遍历一次HashMap,把所有需要添加的父->子关系收集起来(只克隆哈希字符串,开销极小);
- 再遍历收集到的关系,批量修改HashMap的children字段。
修改后的代码:
use std::collections::HashMap; type Hash = String; type Hashes = Vec<Hash>; struct RawNode { parents : Hashes, children : Hashes, // 其他字段... } fn make_raw() -> HashMap<Hash, RawNode> { // 原有的读取逻辑 HashMap::new() } fn make_bi() -> HashMap<Hash, RawNode> { let mut raw_graph = make_raw(); // 第一步:收集所有父哈希到子哈希的映射 let mut child_links = Vec::new(); for (child_hash, node) in raw_graph.iter() { for parent_hash in node.parents.iter() { // 只克隆哈希字符串,RawNode本身不会被克隆 child_links.push((parent_hash.clone(), child_hash.clone())); } } // 第二步:批量更新父节点的children字段 for (parent_hash, child_hash) in child_links { if let Some(parent_node) = raw_graph.get_mut(&parent_hash) { parent_node.children.push(child_hash); } } raw_graph }
这里的克隆开销可以忽略:Rust的String是堆分配结构,但clone()只会复制指针、长度和容量,只有当后续修改字符串时才会触发深拷贝,而哈希字符串通常不会被修改,本质是浅拷贝级别的开销。
为什么编译器不认可你的原始逻辑?
Rust的静态借用检查是基于容器整体的,而不是单个条目。iter()返回的迭代器依赖HashMap的结构稳定,如果在迭代过程中允许修改容器(哪怕是修改其他条目),可能会触发HashMap的扩容,导致迭代器持有的指针失效,进而引发内存安全问题。编译器不会为了你的特殊逻辑放宽规则——它要保证所有场景下的内存安全。
内容的提问来源于stack exchange,提问作者Adrian May
相关产品推荐
相关产品推荐

