2016-11-28 3 views
3

HashMapを使用してインメモリデータベースを作成しようとしています。私は、構造体Person持っている:メモリデータベース設計で

struct Person { 
    id: i64, 
    name: String 
} 

impl Person { 
    pub fn new(id: i64, name: &str) -> Person { 
     Person { id: id, name: name.to_string() } 
    } 

    pub fn set_name(&mut self, name: &str) { 
     self.name = name.to_string(); 
    } 
} 

をそして私は、構造体Database持っている:このデータベースを使用するように

use std::sync::Arc; 
use std::sync::Mutex; 
use std::collections::HashMap; 

struct Database { 
    db: Arc<Mutex<HashMap<i64, Person>>> // access from different threads 
} 

impl Database { 
    pub fn new() -> Database { 
     db: HashMap::new() 
    } 

    pub fn add_person(&mut self, id: i64, person: Person) { 
     self.spots.lock().unwrap().insert(id, person); 
    } 

    pub fn get_person(&self, id: i64) -> Optional<&mut Person> { 
     self.spots.lock().unwrap().get_mut(&id) 
    } 
} 

とコードを:

let mut db = Database::new(); 
db.add_person(1, Person::new(1, "Bob")); 

私は人の名前を変更したい:

let mut person = db.get_person(1).unwrap(); 
person.set_name("Bill"); 

complete code in the Rust playground

コンパイルすると、私は錆寿命の問題を取得する:

error: borrowed value does not live long enough 
    --> <anon>:34:9 
    | 
34 |   self.db.lock().unwrap().get_mut(&id) 
    |   ^^^^^^^^^^^^^^^^^^^^^^^ temporary value created here 
35 |  } 
    |  - temporary value only lives until here 
    | 
note: borrowed value must be valid for the anonymous lifetime #1 defined on the block at 33:61... 
    --> <anon>:33:62 
    | 
33 |  pub fn get_person(&self, id: i64) -> Option<&mut Person> { 
    |               ^

どのようにこのアプローチを実装するために?

答えて

8

コンパイラは、Rustによって強制される正しさのモデルに違反し、クラッシュする可能性があるため、コードを拒否します。 1つは、get_person()がコンパイルされた場合、2つのスレッドから呼び出され、mutexを保護せずに基礎となるオブジェクトを変更し、内部のStringオブジェクトのデータ競合を引き起こす可能性があります。コードを修正するには

let mut ref1 = db.get_person(1).unwrap(); 
let mut ref2 = db.get_person(1).unwrap(); 
// ERROR - two mutable references to the same object! 

let vec: Vec<Person> = vec![]; 
vec.push(*ref1); // move referenced object to the vector 
println!(*ref2); // CRASH - object already moved 

、あなたは以下の制約を満たすためにあなたのデザインを調整する必要があります:1のようなものを実行しても、シングルスレッドのシナリオで大混乱をもたらす可能性が悪いこと

  • ませ参照被参照対象よりも寿命を長くすることができる。
  • 多くても、単一の変更可能な参照はオブジェクトを参照することができます。

add_personメソッドは、オブジェクトを渡してデータベースに移動するため、すでに両方のルールに準拠しています。

不変参照を返すようにget_person()を変更した場合はどうなりますか?

pub fn get_person(&self, id: i64) -> Option<&Person> { 
    self.db.lock().unwrap().get(&id) 
} 

この一見無邪気なバージョンでもまだコンパイルされません!それは最初のルールに違反しているからです。 Rustは、データベースがヒープと参照カウントに割り当てられているので、参照がデータベース自体よりも長く存続することを静的に証明できないため、いつでも削除できます。しかし、明らかにデータベースの寿命を延ばすことができなかった参照の存続期間を何らかの形で明示的に宣言することができたとしても、ミューテックスのロックを解除した後に参照を保持することでデータ競争が可能になります。 get_person()を実装する方法はなく、スレッドの安全性を保持しています。

スレッドセーフな読み取りの実装では、データのコピーを返すことができます。 Personclone()方法を実施することができるとget_person()は、このようにそれを呼び出すことができる。

#[derive(Clone)] 
struct Person { 
    id: i64, 
    name: String 
} 
// ... 

pub fn get_person(&self, id: i64) -> Option<Person> { 
    self.db.lock().unwrap().get(&id).cloned() 
} 
変化のこの種の方法は、可変を得る明確な目的のために使用される get_person()の他の用途の場合、動作しません

データベース内の人を変更するための参照。共有リソースに対する変更可能な参照を取得すると、2番目のルールに違反し、上記のようにクラッシュする可能性があります。安全にするにはいくつかの方法があります。一つは、各Personフィールドを設定するために、データベース内のプロキシを提供することである:

Person上のフィールドの数が増えるにつれ
pub fn set_person_name(&self, id: i64, new_name: String) -> bool { 
    match self.db.lock().unwrap().get_mut(&id) { 
     Some(mut person) => { 
      person.name = new_name; 
      true 
     } 
     None => false 
    } 
} 

、これはすぐに退屈になるだろう。また、アクセスごとに個別のミューテックスロックを取得する必要があるため、速度が遅くなる可能性もあります。

エントリの変更を実装するより良い方法があります。変更可能な参照を使用すると、に違反することを覚えておいてください。 Rustは、参照が使用されているブロックを "エスケープ"しないことを証明できます。変更可能な参照を返すget_person()の代わりに、modify_person()を導入して、に変更可能な参照を渡すことができます。この参照は、それが好きなことを行うことができます。たとえば:

pub fn modify_person<F>(&self, id: i64, f: F) where F: FnOnce(Option<&mut Person>) { 
    f(self.db.lock().unwrap().get_mut(&id)) 
} 

使用量は次のようになります。

fn main() { 
    let mut db = Database::new(); 

    db.add_person(1, Person::new(1, "Bob")); 
    assert!(db.get_person(1).unwrap().name == "Bob"); 

    db.modify_person(1, |person| { 
     person.unwrap().set_name("Bill"); 
    }); 
} 

最後に、あなたがそれを検査する唯一の理由Personをクローニングget_person()の性能を心配している場合、それはに簡単ですTほか

pub fn read_person<F, R>(&self, id: i64, f: F) -> R 
    where F: FnOnce(Option<&Person>) -> R { 
    f(self.db.lock().unwrap().get(&id)) 
} 

get_person()非コピーの代替として機能modify_personの不変のバージョンを作成Person,read_personへの共有参照を許可することで、クロージャが選択した値、通常は受信したオブジェクトから受け取る値を返すこともできます。その使用法は、modify_personの使用法と似ており、値を返す可能性が追加されます。

// if Person had an "age" field, we could obtain it like this: 
let person_age = db.read_person(1, |person| person.unwrap().age); 

// equivalent to the copying definition of db.get_person(): 
let person_copy = db.read_person(1, |person| person.cloned()); 
関連する問題