Rust Book第13.1章,用泛型参数包装HashMap



我已经用HashMap<u32, u32>成功地实现了Cacher,我想按照书中的建议用完全通用的类型来实现它。目前我的代码看起来是这样的,但它没有编译,我没有想法。编译错误与在value()方法中移动v有关,如果我将整个代码更改为接受&u32而不是u32,则问题仍然存在。

use std::{thread, time::Duration, collections::HashMap, hash::Hash};
struct Cacher<T, K, V>
where
T: Fn(K) -> V,
K: Eq + Hash
{
calculation: T,
values: HashMap<K, V>,
}
impl<T, K, V> Cacher<T, K, V>
where
T: Fn(K) -> V,
K: Eq + Hash
{
fn new(calculation: T) -> Self {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, arg: K) -> V {
if let Some(v) = self.values.get(&arg) {
*v
} else {
let v = (self.calculation)(arg);
self.values.insert(arg, v);
v
}        
}
}
// rest of the code
fn main() {
let simulated_user_specified_value = 10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_closure: Cacher<_, u32, u32> = Cacher::new(|num: u32| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
});
if intensity < 25 {
println!("Today do {} pushups!", expensive_closure.value(intensity));
println!("Next, do {} situps!", expensive_closure.value(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today run for {} minutes!", expensive_closure.value(intensity));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn call_with_different_values() {
let mut c = Cacher::new(|a| a);
let v1 = c.value(1);
let v2 = c.value(2);
assert_eq!(v1, 1);
assert_eq!(v2, 2);
}
}

编辑:修复错误的代码的最终版本:

use std::{thread, time::Duration, collections::HashMap, hash::Hash};
struct Cacher<T, K, V>
where
T: Fn(&K) -> V,
K: Eq + Hash
{
calculation: T,
values: HashMap<K, V>,
}
impl<T, K, V> Cacher<T, K, V>
where
T: Fn(&K) -> V,
K: Eq + Hash
{
fn new(calculation: T) -> Self {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, arg: K) -> &V {
self.values.entry(arg).or_insert_with_key(&self.calculation)       
}
}
// rest of the code
fn main() {
let simulated_user_specified_value = 10;
let simulated_random_number = 7;
generate_workout(simulated_user_specified_value, simulated_random_number);
}
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_closure: Cacher<_, u32, u32> = Cacher::new(|num: &u32| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
*num
});
if intensity < 25 {
println!("Today do {} pushups!", expensive_closure.value(intensity));
println!("Next, do {} situps!", expensive_closure.value(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!("Today run for {} minutes!", expensive_closure.value(intensity));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn call_with_different_values() {
let mut c = Cacher::new(|a| *a);
let v1 = c.value(1);
assert_eq!(*v1, 1);
let v2 = c.value(2);
assert_eq!(*v2, 2);
}
}

在Rust中,默认情况下,对象是不可复制的。

假设我们想要缓存类型为Vec<i32>的值。在Cacher::value()中,我们取arg: Vec<i32>。现在假设密钥已经在缓存映射中。我们可以获得对它的引用,但如果我们想返回类型V,就像现在一样,即Vec<i32>,我们必须获得引用的自有版本。现在假设如果执行*v的代码进行编译会发生什么。当调用方销毁(丢弃)该值时,Vec的存储将被释放,并且我们将在缓存中有一个释放的Vec!下次我们将查找相同的密钥时,我们将获得一个无效的Vec,我们尝试执行的任何操作都将是"未定义的行为"!这太可怕了,正是Rust设计用来防止的那种错误。

类似的事情也发生在钥匙上。假设我们的自变量是Vec<i32>类型。当我们将它传递给(self.calculation)(arg)的计算器时,它将释放它,当我们在下一行尝试将它存储在缓存(self.values.insert(arg, v))中时,我们将存储一个无效的Vec

有两种方法可以解决这个问题,正确的方法取决于你问题的具体细节。

第一种方法是不要让他们(消费者和计算器)破坏价值。换言之,我们不会让他们更改值,只会让检查(从技术上讲,我们可以让他们更改,但不能让销毁,但这会导致不同的问题)。或者,用Rust术语来说,我们会给它们一个引用,而不是拥有的值。这将有一个明显的缺点,即他们无法改变它:在某些情况下,他们可能被要求改变,然后第二种选择可能更可取。

试图在代码中对此进行建模会给我们带来一些错误:

T: Fn(K) -> V,
// Replace with
T: Fn(&K) -> V,
// And
fn value(&mut self, arg: K) -> &V {
if let Some(v) = self.values.get(&arg) {
v
} else {
let v = (self.calculation)(&arg);
self.values.insert(arg, v);
&v
}        
}

游乐场。

error[E0502]: cannot borrow `self.values` as mutable because it is also borrowed as immutable
--> src/lib.rs:29:13
|
24 |     fn value(&mut self, arg: K) -> &V {
|              - let's call the lifetime of this reference `'1`
25 |         if let Some(v) = self.values.get(&arg) {
|                          --------------------- immutable borrow occurs here
26 |             v
|             - returning this value requires that `self.values` is borrowed for `'1`
...
29 |             self.values.insert(arg, v);
|             ^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
error[E0515]: cannot return reference to local variable `v`
--> src/lib.rs:30:13
|
30 |             &v
|             ^^ returns a reference to data owned by the current function
error[E0382]: borrow of moved value: `v`
--> src/lib.rs:30:13
|
28 |             let v = (self.calculation)(&arg);
|                 - move occurs because `v` has type `V`, which does not implement the `Copy` trait
29 |             self.values.insert(arg, v);
|                                     - value moved here
30 |             &v
|             ^^ value borrowed here after move

这有点让人不知所措!

第一个错误实际上是Rust借用检查器的当前限制:代码没有编译器认为的问题,但编译器无法证明这一点。未来的项目将帮助编译器实现这一点,但与此同时,我们可以做一些事情:Rust的hashmap有一个专门为insert-if-not-already-there这种情况设计的构造:条目API。使用它,我们的代码看起来像(操场):

fn value(&mut self, arg: K) -> &V {
// `&self.calculation` is really equal here to `|arg| (self.calculation)(arg)`, just shorter.
self.values.entry(arg).or_insert_with_key(&self.calculation)
}

神奇的是,我们还解决了另外两个问题!这很好,但为了了解情况,我还是会解释问题所在。

我们将v移到hashmap中,然后使用它。这就是问题所在。再次考虑v具有类型Vec<i32>的情况。如果我们能使用它,我们需要在之后销毁它——但它在地图内,我们不能销毁它!出于这个原因,编译器拒绝了我们的代码,并出现了第三个错误。

至于第二个错误,我们返回对v的引用。但是v是一个局部变量,并且在函数结束时被销毁——所以我们将返回对无效数据的引用!

修复它们不是小事(甚至是不可能的),所以我不想在这里解释。

第二种解决方法是首先思考";为什么它与i32、非通用版本一起工作;?

答案很简单:它之所以有效,是因为";销毁";CCD_ 25。更正式地说,i32并没有持有某些资源,就像Vec持有分配一样:它只是普通数据,复制它——只是一个逐位的简单复制——总是可以的。或者,用Rust术语来说,i32实现Copy

这就提出了一个问题:我们能在通用版本中表达这个规则吗?即说";我们希望只允许始终有效的类型进行复制,即实现Copy"事实证明,答案是肯定的!我们只需要添加一个where Type: Copy(操场):

where
T: Fn(K) -> V,
K: Eq + Hash + Copy,
V: Copy,

有一种方法可以概括这一点:目前,这只允许逐位复制,也就是说,只允许完全相同的位模式可以自由复制的类型。但我们也可以允许自定义复制代码,并允许像Vec这样的东西,在保存资源的同时,可以通过分配新内存和复制内容来复制。该特性被称为Clone,但它要求我们在复制值时更加明确(注意,每个Copy也是Clone,因此它也适用于i32等类型):

where
T: Fn(K) -> V,
K: Eq + Hash + Clone,
V: Clone,
fn value(&mut self, arg: K) -> V {
if let Some(v) = self.values.get(&arg) {
v.clone()
} else {
let v = (self.calculation)(arg.clone());
self.values.insert(arg, v.clone());
v
}
}

游乐场。

如果您不确定该选择什么选项,或者不知道代码的客户端需要什么,请选择第一个选项。它更通用,因为它允许任何类型(而不仅仅是实现Clone的类型),如果他们需要它,他们总是可以.clone()值,然后对它进行变异,即使这有点不方便。

最新更新