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

Rust标准库HashMap插入后返回键值不可变引用的实现困境

编辑说明

经过一番思考,我开始怀疑这类API在Rust的借用检查器下是否从根本上可行。

我期望HashMap能提供的API大致如下:

  • fn insert_and<'a> (&'a mut self, key: K, value: V) -> (&'a K, &'a V)
  • fn entry_view(&mut self, key: K) -> EntryView<'_, K, V>

目标是无需额外查找即可获取插入后键和值的引用,最好类似Entry API的实现方式。

但由于需要实现「不存在则插入键值对」的逻辑,必须获取self的可变引用(否则就只是多此一举的get()操作)!

而这意味着生命周期'a绑定到可变引用,返回的&'a K也会被借用检查器当作可变引用处理!(参考:引用降级相关讨论)

而且——可能是我的误解——即使使用不安全代码,Rust似乎也无法将绑定到self的可变引用降级为不可变引用。例如:

fn foo<'a, 'b> (&'a mut self) -> &'b Foo {
    unsafe { ... }
}

无法正常工作——'b未绑定到self,会允许在&'b Foo生效期间进行可变借用或销毁操作。

因此,上述设想的API虽然逻辑合理,但实际会被当作对self的可变引用,这完全违背了我们想要实现的、Entry API不具备的功能。

没想到会这样。看来IndexMap确实是避开这些生命周期问题的最佳选择。


问题描述

我遇到了两个看似简单的问题,但在标准库(或hashbrown的)HashMap中找不到简便的实现方式。IndexMap crate似乎是可行的解决方案,但这类问题应该能在标准库中实现吧?我是不是遗漏了什么?

第一个问题:获取可返回的已插入键的引用

struct Foo<K, V> {
    map: HashMap<K, V>
}

impl<K, V> Foo<K, V>
where
    K: Hash + Eq
{
    fn foo(&mut self, key: K, value: V) -> (&K, &V) {
        //Insert and return (&K, &V) somehow
    }
}

简单的方法是为K添加Clone约束,插入前克隆键,但这会:a) 引入不必要的约束;b) 效率较低(克隆+两次查找)

我期望的实现是:

  • 插入后无需额外查找即可获取键的引用(最好类似Entry API)
  • 引用的生命周期绑定到所属的HashMap(而非OccupiedEntry等结构体的生命周期)

我首先想到用Entry API——即将在Rust 1.83中稳定的entry_insert特性看起来很有希望,它返回OccupiedEntry而非像其他函数那样返回&'a mut V。

但Entry结构体只能获取&K(生命周期受限于Entry对象),无法获取&'a K(类似into_mut函数返回的引用)。

我找到的另一种方法是使用raw_entry特性,这看起来很可行:该函数返回(&'a mut K, &'a mut V),可转换为(&'a K, &'a V)。很棒!

但……截至8月17日,该特性似乎已进入弃用流程,这意味着它很可能很快会从nightly和stable版本中移除(当然,我可能理解有误,我对Rust的流程并不十分熟悉)。一条评论提到,高级功能可通过entry_ref实现,低级功能可通过HashTable实现——但我的需求并不复杂,算不上“低级功能”,而且hashbrown crate中的entry_ref实现也无法获取&'a K,和标准库的版本类似。

当然,如果要引入hashbrown,不如直接使用该crate中的raw_entry——但这(以及标准库中的对应函数)又出现了新问题:即使将可变引用转换为不可变引用,Rust的借用检查器仍会将其视为对map的可变引用。

第二个问题:获取已插入键的不可变引用

struct Bar<'a, K> {
    map: &'a HashMap<K, K>,
    entry: OccupiedEntry<'a, K, K> // or some variant
}

struct Foo<K> {
    map: HashMap<K, K>
}

impl<K, V> Foo<K, K>
where
    K: Hash + Eq
{
    fn foo<'a> (&'a mut self, key: K, value: K) -> Bar<'a, K> {
        let entry = self.map.entry(key)
            .insert_entry(value)
        
        Bar {
            // This isn't possible because `entry` acts as a mutable reference to `self.map`
            map: &self.map,
            entry
        }
    }
}

(注意:现在值类型与键类型均为K,以便更好地说明问题;第一个问题中使用V是为了避免混淆)

如你所见,这是我在尝试解决上述问题时遇到的困境:这种方式无需依赖Entry结构体,但我还需要存储hashmap的引用,以便用entry中的值进行查找。

这需求可能有点特殊,但本质上是要获取已插入条目的视图,并将其当作不可变引用处理;即需要:

  • 插入后无需额外查找即可获取键的引用(最好类似Entry API)
  • 该引用被视为对HashMap的不可变引用

entry、entry_ref和raw_entry_mut的所有版本都以&mut self作为参数,这意味着即使转换为不可变引用,它们仍会被视为对HashMap的可变引用。这可以理解,因为它们确实具备修改条目的功能,但对我们来说很不幸,这意味着无法使用上述变通方法。

更糟的是,这种限制意味着即使不满足第一个需求也无法解决问题:由于条目被视为对结构体的可变引用,借用检查器会限制我们在持有Entry时执行get()操作——也无法用获取到的Entry执行get_key_value()。

考虑到这些复杂性,IndexMap似乎是解决上述问题的最佳选择。我不确定insert_full和get_index是否会执行两次查找,但纠结这个可能属于过早优化。不过,我们真的需要引入额外的crate吗?这种基础功能竟然无法在标准库中实现?


内容的提问来源于stack exchange,提问作者riveroon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 22:47:32