rust 在HashMaps中,结构属性作为键,结构本身作为值

a8jjtwal  于 8个月前  发布在  其他
关注(0)|答案(1)|浏览(94)

下面是一个更复杂的代码片段,其思想是加载一个SQL表,并设置一个hashmap,其中一个表结构字段作为键,并将结构作为值(实现细节并不重要,因为如果我克隆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`.
use std::collections::HashMap;

struct Foo {
    a: String,
    b: String,
}

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

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

    let foo_vec = vec![foo_1, foo_2];

    let mut foo_hashmap = HashMap::new();

    foo_vec.into_iter().for_each(|foo| {
        foo_hashmap.insert(foo.a, foo);  // foo.a.clone() will make this compile
    });
}

结构Foo不能实现Copy,因为它的字段是String。我试着用Rc::new(RefCell::new()) Package foo.a,但后来陷入了RefCell<String>缺少特征Hash的陷阱,所以目前我不确定是否为结构体字段使用其他东西(Cow能工作吗?),或者在for_each循环中处理该逻辑。

4nkexdtk

4nkexdtk1#

这里至少有两个问题:首先,生成的HashMap<K, V>将是一个自引用结构,因为K借用了V;在SA上有关于这个陷阱的many问题和答案。其次,即使你可以构造这样的HashMap,你也很容易破坏HashMap提供的保证,它允许你修改V,同时假设K总是保持不变:没有办法得到一个HashMap&mut K,但你可以得到一个&mut V;如果K实际上是一个&V,那么可以很容易地将K修改为V(通过改变Foo.a的方式)并破坏Map。
一种可能性是将Foo.aString更改为Rc<str>,您可以以最小的运行时成本克隆它,以便将值放入KV中。由于Rc<str>Borrow<str>,您仍然可以通过&str在Map中查找值。这仍然有理论上的缺点,你可以通过从Map中获取&mut Foostd::mem::swap来破坏Map,这使得不可能从它的键中查找正确的值;但你得故意这么做
另一种选择是实际使用HashSet而不是HashMap,并为Foo使用一个行为类似于Foo.a的newtype。你必须像这样实现PartialEqEqHash(以及Borrow<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);
}

请注意,由于上述原因,HashSet无法获取&mut FooEntry。您必须使用RefCell(并阅读HashSet的文档对此的说明)。
第三个选项是简单地将clone()转换为foo.a。鉴于上述情况,这可能是最简单的解决方案。如果使用Rc<str>不会因为其他原因而困扰你,这将是我的选择。

  • 备注 *:如果您不需要修改a和/或b,则Box<str>String小一个机器字。

相关问题