gpt4 book ai didi

Struct property as key and the struct itself as value in HashMaps(结构属性作为键,结构本身作为HashMaps中的值)

转载 作者:bug小助手 更新时间:2023-10-25 15:07:00 25 4
gpt4 key购买 nike



The following is a snippet of a more complicated code, the idea is loading a SQL table and setting a hashmap with one of the table struct fields as the key and keeping the structure as the value (implementation details are not important since the code works fine if I clone the String, however, the Strings in the DB can be arbitrarily long and cloning can be expensive).

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


The following code will fail with

以下代码将失败,并显示


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
});
}

The struct Foo cannot implement Copy since its fields are String. I tried wrapping foo.a with Rc::new(RefCell::new()) but later went down the pitfall of missing the trait Hash for RefCell<String>, so currently I'm not certain in either using something else for the struct fields (will Cow work?), or to handle that logic within the for_each loop.

Struct Foo无法实现复制,因为它的字段是字符串。我尝试用rc::new(RefCell::New())包装foo.a,但后来又犯了一个缺陷,即缺少RefCell<字符串>的特征散列,所以目前我不确定是对结构字段使用其他方法(Cow可以工作吗?),还是在for_each循环中处理该逻辑。


更多回答

I've hit this exact issue before. It's a bit of a mess. Personally, I just take the hit and clone the key, to cut out that dependency altogether. Minor performance hit but you can be confident your code is correct.

我以前遇到过这个问题。这里有点乱。就我个人而言,我只是接受了打击并克隆了密钥,以彻底消除这种依赖。性能受到轻微影响,但您可以确信您的代码是正确的。

This can't work, because as soon as you have an immutable borrow against a value in the hashmap, you can never mutably borrow the hashmap again -- otherwise you could cause a reallocation that invalidates the reference.

这是行不通的,因为一旦您对哈希图中的值有了一个不可变的借入,您就再也不能可变地借用哈希图了--否则您可能会导致重新分配,从而使引用无效。

优秀答案推荐

There are at least two problems here: First, the resulting HashMap<K, V> would be a self-referential struct, as the K borrows V; there are many questions and answers on SA about the pitfalls of this. Second, even if you could construct such a HashMap, you'd easily break the guarantees provided by HashMap, which allows you to modify V while assuming that K always stays constant: There is no way to get a &mut K for a HashMap, but you can get a &mut V; if K is actually a &V, one could easily modify K through V (by ways of mutating Foo.a ) and break the map.

这里至少有两个问题:首先,产生的HashMap 将是一个自引用结构,因为K借用了V;关于这一缺陷,SA上有许多问题和答案。其次,即使您可以构造这样的HashMap,您也很容易打破HashMap提供的保证,它允许您在假设K始终保持不变的情况下修改V:无法为HashMap获得&mut K,但您可以获得&mut V;如果K实际上是a&V,则可以通过V(通过突变Foo.a的方式)轻松地修改K并破坏映射。


One possibility is to change Foo.a from a String to a Rc<str>, which you can clone with minimal runtime cost in order to put the value both in the K and into V. As Rc<str> is Borrow<str>, you can still look up values in the map by means of &str. This still has the - theoretical - downside that you can break the map by getting a &mut Foo from the map and std::mem::swap the a, which makes it impossible to look up the correct value from its keys; but you'd have to do that deliberately.

一种可能性是将Foo.a从字符串更改为rc ,您可以用最小的运行时成本克隆它,以便将K和V中的值都放入。当rc 是borrow 时,您仍然可以通过&str在映射中查找值。这仍然有理论上的缺点,即您可以通过从map和std::Mem::交换a中获取&mut Foo来打破映射,这使得无法从其键中查找正确值;但您必须故意这样做。


Another option is to actually use a HashSet instead of a HashMap, and use a newtype for Foo which behaves like a Foo.a. You'd have to implement PartialEq, Eq, Hash (and Borrow<str> for good measure) like this:

另一种选择是实际使用HashSet而不是HashMap,并为Foo使用行为类似于Foo.a的Newtype。您必须像这样实现PartialEq、Eq、Hash(以及Borry ):


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);
}

Notice that HashSet provides no way to get a &mut FooEntry due to the reasons described above. You'd have to use RefCell (and read what the docs of HashSet have to say about this).

请注意,由于上述原因,HashSet不提供获取&mut FooEntry的方法。您必须使用RefCell(并阅读HashSet的文档对此有何评论)。


The third option is to simply clone() the foo.a as you described. Given the above, this is probably the most simple solution. If using an Rc<str> doesn't bother you for other reasons, this would be my choice.

第三种选择是简单地克隆()foo.a,如您所述。鉴于上述情况,这可能是最简单的解决方案。如果出于其他原因,您不介意使用rc ,这将是我的选择。


Sidenote: If you don't need to modify a and/or b, a Box<str> instead of String is smaller by one machine word.

SideNote:如果不需要修改a和/或b,则Box 比字符串小一个机器字。


更多回答

That's quite a neat trick with using HashSet and a newtype. Brilliant. I don't know any other standard libraries with this level of capability.

这是使用HashSet和Newtype的一个相当巧妙的技巧。非常出色。我不知道其他有这种能力的标准库。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com