【问题标题】:Shared ownership of an str between a HashMap and a Vec在 HashMap 和 Vec 之间共享 str 的所有权
【发布时间】:2017-02-17 10:57:23
【问题描述】:

我来自 Java/C#/JavaScript 背景,我正在尝试实现一个 Dictionary,它将为每个传递的字符串分配一个永不改变的 id。字典应该能够通过指定的 id 返回一个字符串。这允许在文件系统中更有效地存储一些具有大量重复字符串的数据,因为只会存储字符串的 id 而不是整个字符串。

我认为带有 HashMapVec 的结构可以,但结果比这更复杂。

我开始使用&str 作为HashMap 的键和Vec 的项目,如下例所示。 HashMap 的值用作Vec 的索引。

pub struct Dictionary<'a> {
    values_map: HashMap<&'a str, u32>,
    keys_map: Vec<&'a str>
}

impl<'a> Dictionary<'a> {
    pub fn put_and_get_key(&mut self, value: &'a str) -> u32 {
        match self.values_map.get_mut(value) {
            None => {
                let id_usize = self.keys_map.len();
                let id = id_usize as u32;
                self.keys_map.push(value);
                self.values_map.insert(value, id);
                id
            },
            Some(&mut id) => id
        }
    }
}

这工作得很好,直到发现strs 需要存储在某个地方,最好也存储在同一个struct 中。我尝试将Box&lt;str&gt; 存储在Vec&amp;'a strHashMap

pub struct Dictionary<'a> {
    values_map: HashMap<&'a str, u32>,
    keys_map: Vec<Box<str>>
}

借用检查器当然不允许这样做,因为当从 Vec 中删除项目时(或者实际上有时将另一个项目添加到 Vec但这是一个题外话)。

我知道我需要编写unsafe 代码或使用某种形式的共享所有权,其中最简单的一种似乎是RcRc&lt;Box&lt;str&gt;&gt; 的用法看起来像是引入了双重间接,但目前似乎没有简单的方法来构造 Rc&lt;str&gt;

pub struct Dictionary {
    values_map: HashMap<Rc<Box<str>>, u32>,
    keys_map: Vec<Rc<Box<str>>>
}

impl Dictionary {
    pub fn put_and_get_key(&mut self, value: &str) -> u32 {
        match self.values_map.get_mut(value) {
            None => {
                let id_usize = self.keys_map.len();
                let id = id_usize as u32;
                let value_to_store = Rc::new(value.to_owned().into_boxed_str());
                self.keys_map.push(value_to_store);
                self.values_map.insert(value_to_store, id);
                id
            },
            Some(&mut id) => id
        }
    }
}

就所有权语义而言,一切似乎都很好,但上面的代码无法编译,因为HashMap 现在需要Rc,而不是&amp;str

error[E0277]: the trait bound `std::rc::Rc<Box<str>>: std::borrow::Borrow<str>` is not satisfied
  --> src/file_structure/sample_dictionary.rs:14:31
   |
14 |         match self.values_map.get_mut(value) {
   |                               ^^^^^^^ the trait `std::borrow::Borrow<str>` is not implemented for `std::rc::Rc<Box<str>>`
   |
   = help: the following implementations were found:
   = help:   <std::rc::Rc<T> as std::borrow::Borrow<T>>

问题:

  1. 有没有办法构造一个Rc&lt;str&gt;
  2. 还有哪些其他结构、方法或途径可以帮助解决此问题。本质上,我需要一种方法来有效地存储两个映射 string-by-idid-by-string 并能够通过 &amp;str 检索 id,即没有任何过多的分配。

【问题讨论】:

标签: hashmap rust


【解决方案1】:

有没有办法构造一个Rc&lt;str&gt;

很烦人,我不知道。 Rc::new 需要 Sized 参数,我不确定这是实际限制,还是只是被遗忘的东西。

还有哪些其他结构、方法或途径可以帮助解决这个问题?

如果您查看get 的签名,您会注意到:

fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
    where K: Borrow<Q>, Q: Hash + Eq

因此,如果K 实现Borrow&lt;str&gt;,则可以通过&amp;str 进行搜索。

String 实现了Borrow&lt;str&gt;,所以最简单的解决方案是简单地使用String 作为键。当然,这意味着您实际上将拥有两个 String 而不是一个......但这很简单。当然,StringBox&lt;str&gt; 更易于使用(尽管它多使用 8 个字节)。

如果您想削减此成本,您可以改用自定义结构:

#[derive(Clone, Debug)]
struct RcStr(Rc<String>);

然后为它实现Borrow&lt;str&gt;。然后,每个键将有 2 个分配(Rc 有 1 个,String 有 1 个)。根据String 的大小,它可能会消耗更少或更多的内存。


如果你想更进一步(为什么不呢?),这里有一些想法:

  • 在单个堆分配中实现您自己的引用计数字符串,
  • 对插入到Dictionary 中的切片使用单个竞技场,
  • ...

【讨论】:

  • 非常感谢,原来我只是解决方案的一步——我尝试为Rc&lt;Box&lt;str&gt;&gt; 本身实现Borrow&lt;str&gt;,编译器不允许,所以我只需要将Rc 包装到我自己的struct 中,现在它可以工作了!由于不必要的数据重复,我不想使用Strings 作为键,因为可能会有数千个字符串。关于go further注释——第二个想法非常有趣,所以也许我可以实现一个相当大的字符串来存储整个Dictionary。稍后我可能会研究一下,谢谢!
猜你喜欢
  • 2016-12-14
  • 2022-01-20
  • 2015-11-20
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
相关资源
最近更新 更多