You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写无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分支没关系。另外,完全一样的代码在函数外部能正常编译。

我已经知道两种解决方案,但都不符合需求:

  1. 使用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))
}
  1. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 21:00:19