在 HashMap 中,结构体属性作为键,结构体本身作为值

Cor*_*nas 1 struct rust

下面是一段更复杂的代码片段,其思想是加载一个 SQL 表并设置一个哈希图,其中一个表结构字段作为键,并将结构保留为值(实现细节并不重要,因为代码工作正常但是,如果我克隆String,数据库中的字符串可以是任意长,并且克隆可能会很昂贵)。

以下代码将失败并显示

error[E0382]: use of partially moved value: `foo`
  --> src/main.rs:24:35
   |
24 |         foo_hashmap.insert(foo.a, foo);
   |                            -----  ^^^ value used here after partial move
   |                            |
   |                            value partially moved here
   |
   = note: partial move occurs because `foo.a` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
Run Code Online (Sandbox Code Playgroud)
error[E0382]: use of partially moved value: `foo`
  --> src/main.rs:24:35
   |
24 |         foo_hashmap.insert(foo.a, foo);
   |                            -----  ^^^ value used here after partial move
   |                            |
   |                            value partially moved here
   |
   = note: partial move occurs because `foo.a` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0382`.
Run Code Online (Sandbox Code Playgroud)

该结构Foo无法实现,Copy因为它的字段是字符串。我尝试用 进行包装foo.aRc::new(RefCell::new())但后来陷入了缺少特征 Hash for 的陷阱RefCell<String>,所以目前我不确定是否使用其他结构字段(会Cow起作用吗?),或者在循环内处理该逻辑for_each

use*_*968 6

这里至少有两个问题:首先,结果HashMap<K, V>将是一个自引用结构,如K借用V;SA 上有很多关于此陷阱的问题和答案。其次,即使您可以构造这样的 a HashMap,您也很容易破坏 提供的保证,这允许您在假设始终保持不变的情况下HashMap进行修改:没有办法为 a 获取 a ,但您可以获取 a ; if实际上是 a ,人们可以轻松地修改(通过 mutating )并破坏映射。VK&mut KHashMap&mut VK&VKVFoo.a

Foo.a一种可能性是从 a更改String为 a Rc<str>,您可以以最小的运行时成本进行克隆,以便将值同时放入 和KV。现在Rc<str>Borrow<str>您仍然可以通过 查找地图中的值&str&mut Foo这仍然有一个理论上的缺点,即您可以通过从映射中获取 a 和std::mem::swap来破坏映射a,这使得无法从其键中查找正确的值;但你必须刻意这样做。

另一种选择是实际使用 aHashSet而不是 a ,并使用行为类似于 a 的HashMap新类型。你必须像这样实现, , (并且为了更好的措施):FooFoo.aPartialEqEqHashBorrow<str>

use std::collections::HashSet;

#[derive(Debug)]
struct Foo {
    a: String,
    b: String,
}

/// A newtype for `Foo` which behaves like a `str`
#[derive(Debug)]
struct FooEntry(Foo);

/// `FooEntry` compares to other `FooEntry` only via `.a`
impl PartialEq<FooEntry> for FooEntry {
    fn eq(&self, other: &FooEntry) -> bool {
        self.0.a == other.0.a
    }
}

impl Eq for FooEntry {}

/// It also hashes the same way as a `Foo.a`
impl std::hash::Hash for FooEntry {
    fn hash<H>(&self, hasher: &mut H)
    where
        H: std::hash::Hasher,
    {
        self.0.a.hash(hasher);
    }
}

/// Due to the above, we can implement `Borrow`, so now we can look up
/// a `FooEntry` in the Set using &str
impl std::borrow::Borrow<str> for FooEntry {
    fn borrow(&self) -> &str {
        &self.0.a
    }
}

fn main() {
    let foo_1 = Foo {
        a: "foo".to_string(),
        b: "bar".to_string(),
    };

    let foo_2 = Foo {
        a: "foobar".to_string(),
        b: "barfoo".to_string(),
    };

    let foo_vec = vec![foo_1, foo_2];

    let mut foo_hashmap = HashSet::new();

    foo_vec.into_iter().for_each(|foo| {
        foo_hashmap.insert(FooEntry(foo));
    });

    // Look up `Foo` using &str as keys...
    println!("{:?}", foo_hashmap.get("foo").unwrap().0);
    println!("{:?}", foo_hashmap.get("foobar").unwrap().0);
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于上述原因,HashSet无法提供获得 a 的方法。&mut FooEntry您必须使用RefCell(并阅读文档HashSet对此的说明)。

第三种选择是简单地clone()按照foo.a您的描述进行操作。鉴于上述情况,这可能是最简单的解决方案。如果使用 anRc<str>不会因为其他原因打扰您,那么这将是我的选择。

旁注:如果不需要修改aand/or b,则 aBox<str>代替String会小一个机器字。