我有一个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
的不同集合,那该怎么办?
答案 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)
使用Rc
(example)
使用Clone
或Copy
创建重复的密钥。
使用HashSet
完成,Sebastian Redl's suggestion进一步增强。 HashSet<K>
实际上只是HashMap<K, ()>
,因此可以将所有权转让给密钥。
答案 1 :(得分:1)
您可以为存储在集合中的项目引入包装类型。
struct FooByK(Foo);
然后实现此结构集所需的各种特征。如果您需要一个由不同成员编制索引的集合,则可以选择不同的包装类型。