如何编写无HashMap二次查询/键克隆的可编译Rust缓存函数?
Rust缓存函数编译错误:不可变与可变借用冲突
我查过很多类似问题,但还是没找到符合需求的方案。下面的Rust缓存函数编译失败,调用insert时触发报错:
fn get_or_load_icon<'a>(icon_cache: &'a mut HashMap<String, TextureHandle>, icon_path: &str) -> &'a TextureHandle { if let Some(texture_cache) = icon_cache.get(icon_path) { texture_cache } else { let texture = load_image(icon_path); icon_cache.insert(icon_path.to_string(), texture); icon_cache.get(icon_path).unwrap() } }
报错信息:
error[E0502]: cannot borrow `*icon_cache` as mutable because it is also borrowed as immutable --> src\main.rs:1108:3 | 1103 | fn get_or_load_icon<'a>(icon_cache: &'a mut HashMap<String, TextureHandle>, icon_path: &str) -> &'a TextureHandle { | -- lifetime `'a` defined here 1104 | if let Some(texture_cache) = icon_cache.get(icon_path) { | ------------------------- immutable borrow occurs here 1105 | texture_cache | ------------- returning this value requires that `*icon_cache` is borrowed for `'a` ... 1108 | icon_cache.insert(icon_path.to_string(), texture); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
我知道icon_cache.get是不可变借用,icon_cache.insert是可变借用,但搞不懂为什么不可变借用在else分支还处于活跃状态,或者生命周期在这里起了什么作用。我原本以为if let块结束后借用就会被释放,和else分支没关系。另外,完全一样的代码在函数外部能正常编译。
我已经知道两种解决方案,但都不符合需求:
- 使用
entry:哪怕能通过&str找到键,还是会克隆icon_path
fn get_or_load_icon<'a>(icon_cache: &'a mut HashMap<String, TextureHandle>, icon_path: &str) -> &'a TextureHandle { icon_cache.entry(icon_path.to_string()).or_insert_with(|| load_image(icon_path)) }
- 使用
contains_key+get:会对HashMap做两次查询
fn get_or_load_icon<'a>(icon_cache: &'a mut HashMap<String, TextureHandle>, icon_path: &str) -> &'a TextureHandle { if icon_cache.contains_key(icon_path) { icon_cache.get(icon_path).unwrap() } else { let texture = load_image(icon_path); icon_cache.insert(icon_path.to_string(), texture); icon_cache.get(icon_path).unwrap() } }
有没有其他方案可以做到既不克隆icon_path,也不做二次查询?还是我写这个函数的方式本身就有根本性错误?
解决方案:使用
raw_entry_mut Rust 1.63及以上版本的HashMap提供了raw_entry_mut方法,它允许你直接操作哈希表的条目,不需要预先克隆键,也只做一次查询。
修改后的代码如下:
use std::collections::hash_map::RawEntryMut; fn get_or_load_icon<'a>(icon_cache: &'a mut HashMap<String, TextureHandle>, icon_path: &str) -> &'a TextureHandle { let entry = icon_cache.raw_entry_mut().from_key(icon_path); match entry { RawEntryMut::Occupied(e) => e.into_mut(), RawEntryMut::Vacant(e) => { let texture = load_image(icon_path); e.insert(icon_path.to_string(), texture) } } }
为什么原来的代码会报错?
Rust的借用检查器是在函数全局层面进行分析的:if let分支返回的&'a TextureHandle要求icon_cache的不可变借用必须持续整个'a生命周期。虽然在else分支里if let的逻辑不会执行,但借用检查器无法在函数内部区分分支的生命周期范围,它会认为整个函数执行期间icon_cache都被不可变借用绑定,因此else分支的可变借用就会冲突。
而在函数外部时,借用的生命周期是局部的(比如只在if块内),所以借用检查器能正确判断借用的范围,不会报错。
方案优势
- 只做一次哈希查询:
raw_entry_mut从键计算哈希后直接定位条目,不管是命中还是插入都只查一次 - 仅在需要插入时克隆键:只有当键不存在时才会执行
icon_path.to_string(),避免了entry方案中每次都克隆键的开销
内容的提问来源于stack exchange,提问作者CanisLupus
相关产品推荐
相关产品推荐

