如何将 Entry API 与仅在 Entry 空置时构建的昂贵密钥一起使用?

Posted

技术标签:

【中文标题】如何将 Entry API 与仅在 Entry 空置时构建的昂贵密钥一起使用?【英文标题】:How do I use the Entry API with an expensive key that is only constructed if the Entry is Vacant? 【发布时间】:2019-01-03 15:18:50 【问题描述】:

是否可以使用Entry API 通过AsRef<str> 获取值,但使用Into<String> 插入它?

这是工作示例:

use std::collections::hash_map::Entry, HashMap;

struct Foo;

#[derive(Default)]
struct Map 
    map: HashMap<String, Foo>,


impl Map 
    fn get(&self, key: impl AsRef<str>) -> &Foo 
        self.map.get(key.as_ref()).unwrap()
    

    fn create(&mut self, key: impl Into<String>) -> &mut Foo 
        match self.map.entry(key.into()) 
            Entry::Vacant(entry) => entry.insert(Foo ),
            _ => panic!(),
        
    

    fn get_or_create(&mut self, key: impl Into<String>) -> &mut Foo 
        match self.map.entry(key.into()) 
            Entry::Vacant(entry) => entry.insert(Foo ),
            Entry::Occupied(entry) => entry.into_mut(),
        
    


fn main() 
    let mut map = Map::default();
    map.get_or_create("bar");
    map.get_or_create("bar");
    assert_eq!(map.map.len(), 1);

playground

我的问题是在get_or_create 中总是会创建一个String,这会导致不需要的内存分配,即使占用的条目不需要它也是如此。有可能以任何方式解决这个问题吗?也许用Cow 的简洁方式?

【问题讨论】:

【参考方案1】:

在 nightly Rust 中,您可以使用不稳定的raw_entry_mut() 功能,它允许这样做:

为 HashMap 创建一个原始条目构建器。

[...]

原始条目对于以下特殊情况很有用:

将拥有的密钥的创建推迟到已知需要时

在稳定的 Rust 中,您可以添加具有相同 API 但稳定的 hashbrown crate。 hashbrown crate 实际上是标准库的 hashmap 的底层实现。

例子:

#![feature(hash_raw_entry)]
use std::collections::HashMap;

#[derive(Hash, PartialEq, Eq, Debug)]
struct NoCopy 
    foo: i32,


impl Clone for NoCopy 
    fn clone(&self) -> Self 
        println!("Clone of: :?", self);
        Self  foo: self.foo 
    


fn main() 
    let no_copy = NoCopy  foo: 21 ;

    let mut map = HashMap::new();

    map.raw_entry_mut()
        .from_key(&no_copy)
        .or_insert_with(|| (no_copy.clone(), 42));

    map.raw_entry_mut()
        .from_key(&no_copy)
        .or_insert_with(|| (no_copy.clone(), 84));

    println!(":#?", map);

应用于您的原始示例:

fn get_or_create<K>(&mut self, key: K) -> &mut Foo
where
    K: AsRef<str> + Into<String>,

    self.map
        .raw_entry_mut()
        .from_key(key.as_ref())
        .or_insert_with(|| (key.into(), Foo))
        .1

【讨论】:

【参考方案2】:

你不能,安全地。这是当前入口API的一个限制,没有很好的解决方案。预期的解决方案是“原始”入口 API。见Stargateur's answer for an example of using it。

使用 Entry API 的唯一稳定解决方案是始终克隆密钥:

map.entry(key.clone()).or_insert(some_value);

在 Entry API 之外,您可以检查地图是否包含值,如果没有则插入:

if !map.contains_key(&key) 
    map.insert(key.clone(), some_value);


map.get(&key).expect("This is impossible as we just inserted a value");

另见:

[Pre-RFC] Abandonning Morals In The Name Of Performance: The Raw Entry API WIP: add raw_entry API to HashMap (50821) Extend entry API to work on borrowed keys. (1769) Add HashMap.entry_or_clone() method (1203)

对于非基于entry 的解决方案,请参阅:

How to avoid temporary allocations when using a complex key for a HashMap? How to implement HashMap with two keys?

【讨论】:

以上是关于如何将 Entry API 与仅在 Entry 空置时构建的昂贵密钥一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

在Web API中进行身份验证与仅在普通Web应用程序中进行身份验证

解释一个Map.Entry

如何将 Entry 输入分配给变量? (屏幕距离不好)

Map.Entry:如何使用?

如何将变量连接到 Entry 小部件?

实体类在set字段时报空指针异常