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的一个相当巧妙的技巧。非常出色。我不知道其他有这种能力的标准库。
我是一名优秀的程序员,十分优秀!