2016-08-17 21 views
4

私はRustでレイジー構築/メモ化評価/キャッシュイディオムを実装しようとしています。Rustでselfを使用する構造のマップエントリを作成する方法

動作の仕方は、データの束を持つ外部型とアクセサメソッドです。アクセサメソッドでは、キャッシュされた計算(キャッシュがある場合)を返すか、計算して後で使用できるようにマップに戻り値を格納する必要があります。キャッシュされた値は外側の値を参照する必要がないので、循環参照の問題はありません。それ自体を構築するためには、外側の値のデータにアクセスする必要があります。

ここで錆のボローチェッカーを通らない完全な例です:

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)) 
                       ^~~~ 

このイディオムを実装する。私は、entry() APIの代わりにget()の戻り値に一致するパターンを試しましたが、依然として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. のように、ハッシュエントリを1回見つけるのにかかる費用のみを支払うようにします。 entry()が正しく行われた場合は、見つからない場合は挿入位置を追跡する必要があります。
  2. 新しく構築された値のコピー数を減らします。これは実行不可能かもしれません、理想的には、私はインプレースの構築があります。
  3. 使用を避けるためunwrap;無意味なパターンマッチなしに、それはです。

私の不器用なコードは、達成できる最高のものですか?

+0

http://stackoverflow.com/q/30681468/155423などの複製となる予定です。それが変更されているので、あなたは 'self'を渡すことはできません。 'entry'が' HashMap'への参照を渡したなら、それ以上の検索を行うことができます。 – Shepmaster

+0

@Shepmaster:ラムダの中の参照の使用のために、私は少し違うと言います。ここでの実行順序は問題ありません。ラムダの実行中、ハッシュマップはフリーズされます。 –

+1

'&Thing'を必要とするか、後で計算するために必要なもの(ここで' .len() ')を"抽出 "していますか?あなたはそれから借りを避けるでしょう。したがって、あなたは 'or_insert_with'を使うことができます。 @MatthieuM。 –

答えて

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

私は質問の精神は、一般的な戦略についての詳細だったと思う、しかし、それでは、またContainedThingを作成するために必要とされるThing内の他のいくつかの状態がありますと仮定しましょう。それは手動でif self.map.contains_key(&key)をチェックするあなたの他のソリューションよりも本当にきれいだかどうか

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

は議論のために任されています。しかし構造全体の代わりにselfの特定のメンバーを借用できるようにするためには、構造化が私が進める戦略となる傾向があります。

+0

ここにシワがあります: 'ContainedThing :: create'は' Thing :: get'自体を呼び出すかもしれません。上から下への動的プログラミングのように、メモの単純化された基本ケースからの再帰的構築を考えてください。 「ContainedThing」の構築中に、特に再帰的に「Thing」というAPIを使用する必要があるため、手動によるチェックを続ける必要があります。 (そして、このシワは、潜在的なサイクルで問題にしてしまいます。私はこのイディオムの問題を解決した後、少し下に行くと思っています。 –

0

したがって、Thingを引数としてContainedThing::createに渡したいという主な動機は、ThingのAPIを使用して構築することでした。しかし、ここで再帰的にメモを取る必要があるので、私はそれを可変的に借りたいと思うので、それはサイクルの問題になります。

したがって、私の分けられたチェック+挿入ロジックがここにあるように見えます。

関連する問題