我已經成功地用 a 實作了快取器,HashMap<u32, u32>并且我想按照本書的建議使用完全通用的型別來實作它。目前我的代碼看起來像這樣,但它沒有編譯,我沒有想法。編譯錯誤與移動方法v有關value(),如果我將整個代碼更改為接受&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);
}
}
uj5u.com熱心網友回復:
在 Rust 中,默認情況下,物件是不可復制的。
假設我們要快取 type 的值Vec<i32>。在里面Cacher::value(),我們拿arg: Vec<i32>。現在假設密鑰已經在快取映射中。我們可以獲得對它的參考,但是如果我們想要回傳 type 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. Butv是一個區域變數,并在函式結束時被銷毀 - 所以我們將回傳對無效資料的參考!
修復它們并非易事(甚至是不可能的),所以我不想在這里解釋。
第二種解決方法是首先思考“為什么它與i32非通用版本一起作業”?
答案很簡單:它之所以有效,是因為“摧毀”一個i32. 更正式地說,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()改變它的值,即使它有點不方便。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/460188.html
上一篇:找不到派生類的Lombok建構式
