制作使用项目字段作为关键字的查找表的惯用方法是什么?

时间:2017-04-29 12:07:49

标签: rust

我有一个Foo的集合。

struct Foo {
    k: String,
    v: String,
}

我想要一个HashMap,其密钥为&foo.k,值为foo

显然,如果不通过引入Foo或克隆/复制Rc来重新设计k,则无法实现。

fn t1() {
    let foo = Foo { k: "k".to_string(), v: "v".to_string() };
    let mut a: HashMap<&str, Foo> = HashMap::new();
    a.insert(&foo.k, foo); // Error
}

get()Playground)滥用HashSet似乎有一种解决方法:

use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher, BuildHasher};
use std::collections::hash_map::Entry::*;

struct Foo {
    k: String,
    v: String,
}

impl PartialEq for Foo {
    fn eq(&self, other: &Self) -> bool { self.k == other.k }
}

impl Eq for Foo {}

impl Hash for Foo {
    fn hash<H: Hasher>(&self, h: &mut H) { self.k.hash(h); }
}

impl ::std::borrow::Borrow<str> for Foo {
    fn borrow(&self) -> &str {
        self.k.as_str()
    }
}

fn t2() {
    let foo = Foo { k: "k".to_string(), v: "v".to_string() };
    let mut a: HashSet<Foo> = HashSet::new();
    a.insert(foo);
    let bar = Foo { k: "k".to_string(), v: "v".to_string() };
    let foo = a.get("k").unwrap();
    println!("{}", foo.v);
}

这很乏味。如果Foo在不同字段中有多个字段和Foo的不同集合,那该怎么办?

2 个答案:

答案 0 :(得分:4)

  

显然,如果不通过引入Foo或克隆/复制Rc来重新设计k,则无法实现。

这是正确的,不可能让HashMap<&K, V>的关键点指向值的某个组成部分。

HashMap拥有密钥和值,从概念上将两者都存储在大向量中。当新值添加到HashMap时,由于散列冲突,可能需要移动这些现有值,可能需要重新分配向量以容纳更多项目。这两个操作都会使任何现有密钥的地址无效,使其指向无效的内存。这将破坏Rust的安全保障,因此不允许。

阅读Why can't I store a value and a reference to that value in the same struct?进行彻底讨论。

此外,trentcl points out HashMap::get_mut允许您获得对的可变引用,这将允许您在不知道地图的情况下更改密钥。正如文档所述:

  

修改密钥是一个逻辑错误,使得由Hash特征确定的密钥散列或其等式(由Eq特征确定)在映射中发生变化。 / p>

解决方法包括:

  • 从结构中删除密钥并单独存储。而不是HashMap<&K, V>其中V是(K, Data),而是存储HashMap<K, Data>。您可以返回一个结构,该结构将对键和值的引用粘合在一起(example

  • 使用Rcexample

  • 分享密钥的所有权
  • 使用CloneCopy创建重复的密钥。

  • 使用HashSet完成,Sebastian Redl's suggestion进一步增强。 HashSet<K>实际上只是HashMap<K, ()>,因此可以将所有权转让给密钥。

答案 1 :(得分:1)

您可以为存储在集合中的项目引入包装类型。

struct FooByK(Foo);

然后实现此结构集所需的各种特征。如果您需要一个由不同成员编制索引的集合,则可以选择不同的包装类型。