2016-08-17 63 views
4

我試圖在Rust中實現一個懶惰構造/ memoized評估/緩存成語。如何懶洋洋地創建地圖條目,其構建在Rust中使用自我

它的工作方式是有一個外部類型,它有一堆數據和一個存取方法。訪問器方法需要返回緩存計算(如果有),或者計算它並將返回值存儲在映射中以供將來使用。緩存的值不需要引用外部值,因此不存在循環引用問題;但它確實需要訪問外部值的數據才能構建自己。

這裏的,其不通過鏽蝕的借檢查一個完整的例子:

use std::collections::HashMap; 
pub struct ContainedThing { 
    count: usize, 
} 

impl ContainedThing { 
    fn create(thing: &Thing) -> ContainedThing { 
     // create uses an arbitrary number of attributes from Thing 
     // it doesn't keep any references after returning though 
     let count = thing.map.len(); 
     ContainedThing { count: count } 
    } 
} 

struct Thing { 
    map: HashMap<i32,ContainedThing>, 
} 

impl Thing { 
    pub fn get(&mut self, key: i32) -> &ContainedThing { 
     self.map.entry(key).or_insert_with(|| ContainedThing::create(&self)) 
    } 
} 

fn main() { 
} 

特定的錯誤是這樣的:

test.rs:20:44: 20:46 error: cannot borrow `self` as immutable because `self.map` is also borrowed as mutable [E0502] 
test.rs:20   self.map.entry(key).or_insert_with(|| ContainedThing::create(&self)) 
                 ^~ 
test.rs:20:9: 20:17 note: mutable borrow occurs here 
test.rs:20   self.map.entry(key).or_insert_with(|| ContainedThing::create(&self)) 
        ^~~~~~~~ 
test.rs:21:5: 21:6 note: mutable borrow ends here 
test.rs:21  } 
      ^
test.rs:20:71: 20:75 note: borrow occurs due to use of `self` in closure 
test.rs:20   self.map.entry(key).or_insert_with(|| ContainedThing::create(&self)) 
                       ^~~~ 

我有一個真的很難搞清楚的好方法實施這個成語。我嘗試了get()而不是entry() API的模式匹配返回值,但仍然與借用檢查器相沖突:match表達式也最終借用了self

我可以重寫get這樣的:

pub fn get(&mut self, key: i32) -> &ContainedThing { 
    if !self.map.contains_key(&key) { 
     let thing = ContainedThing::create(&self); 
     self.map.insert(key, thing); 
    } 
    self.map.get(&key).unwrap() 
} 

但是這是很醜陋(看那個unwrap),似乎需要更多的查找和複印的速度比應該是必要的。理想情況下,我想

  1. 只支付一次查找哈希項的成本。 entry(),做得不錯,應該在未找到時跟蹤插入位置。
  2. 減少新構建值的拷貝數。這可能是不可行的,理想情況下我有一個就地建設。
  3. 避免使用unwrap;沒有無意義的模式匹配,也就是說。

我的笨拙代碼是最好的可以實現嗎?

+0

會是http://stackoverflow.com/q/30681468/155423或類似的副本。你不能傳遞'self',因爲它正在被改變。如果'entry'給你提供了'HashMap'的引用,那麼你可能會進行更多的查找。 – Shepmaster

+0

@Shepmaster:我會說它略有不同,因爲在lambda中使用了引用。這裏的執行順序很好:在執行lambda時,哈希映射被凍結。 –

+1

是否需要借用'&Thing',或者將以前的計算(以下'.len()')從「&Thing」中提取出來,然後「解壓」那麼你會避免借用,因此你可以使用'or_insert_with'。 –

回答

1

答案是它具體取決於您需要在or_insert_with閉包中訪問哪個狀態。問題是or_insert_with肯定無法訪問映射本身,因爲條目api需要映射的可變借用。

如果您只需要ContainedThing::create只是地圖的大小,那麼您只需提前計算地圖大小即可。

impl Thing { 
    pub fn get(&mut self, key: i32) -> &ContainedThing { 
     let map_size = self.map.len(); 
     self.map.entry(&key).or_insert_with(|| { 
      // The call to entry takes a mutable reference to the map, 
      // so you cannot borrow map again in here 
      ContainedThing::create(map_size) 
     }) 
    } 
} 

我認爲問題的精神是更多的一般策略,雖然如此,我們假設有內Thing一些其他的狀態,還需要創建ContainedThing

struct Thing { 
    map: HashMap<i32, ContainedThing>, 
    some_other_stuff: AnotherType //assume that this other state is also required in order to create ContainedThing 
} 

impl Thing { 
    pub fn get(&mut self, key: i32) -> &ContainedThing { 
     //this is the borrow of self 
     let Thing {ref mut map, ref mut some_other_stuff} = *self; 

     let map_size = map.len(); 
     map.entry(key).or_insert_with(|| { // map.entry now only borrows map instead of self 
      // Here you're free to borrow any other members of Thing apart from map 
      ContainedThing::create(map_size, some_other_stuff) 
     }) 
    } 
} 

這是否是真的比手動檢查if self.map.contains_key(&key)您的其他解決方案更清潔是爲辯論。儘管如此,解構往往是我所採取的策略,允許借用self的特定成員而不是整個結構。

+0

這是一個皺紋:'ContainedThing :: create'可能會調用'Thing :: get';從簡單的基礎案例中思考遞歸構造,就像從上到下的動態編程一樣。在構建「ContainedThing」期間使用「Thing」API的必要性 - 特別是遞歸的 - 是我得出的結論是手動檢查將不得不停止。 (是的,這個皺紋使它成爲一個潛在的循環問題,在解決這個習慣用法問題之後,我只是想了一會兒。 –

0

因此,我想要通過Thing作爲參數ContainedThing::create的主要動機是使用Thing的API來幫助構建。然而,事實證明,我希望它可以被借用,因爲我需要遞歸的memoized構造,這使得它成爲一個循環問題。

因此,它看起來像我分離的檢查+插入邏輯是留在這裏。

相關問題