Rust 内存数据库设计

Rust 内存数据库设计,rust,Rust,我正在尝试使用HashMap创建内存中的数据库。我有一个结构人: struct Person { id: i64, name: String, } impl Person { pub fn new(id: i64, name: &str) -> Person { Person { id: id, name: name.to_string(), } } pub

我正在尝试使用
HashMap
创建内存中的数据库。我有一个结构

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();
    }
}
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))
}
我有struct
数据库

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

struct Database {
    db: Arc<Mutex<HashMap<i64, Person>>>,
}

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

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

    pub fn get_person(&self, id: i64) -> Option<&mut Person> {
        self.db.lock().unwrap().get_mut(&id)
    }
}
我想更改
人员的姓名:

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

在编译时,我遇到了一个关于生锈寿命的问题:

error[E0597]:借入值的有效期不够长
-->src/main.rs:39:9
|
39 | self.db.lock().unwrap().get_mut(&id)
|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^临时值的有效期不够长
40 |     }
|-临时价值仅在此之前有效
|
注意:借用值必须在方法体上38:5定义的匿名生存期#1内有效。。。
-->src/main.rs:38:5
|
38 |/pub fn get_person(&self,id:i64)->选项{
39 | | self.db.lock().unwrap().get_mut(&id)
40 | |     }
| |_____^

如何实现这种方法?

编译器拒绝您的代码,因为它违反了Rust强制实施的正确性模型,并可能导致崩溃。首先,如果允许编译
get_person()
,可能会从两个线程调用它,并在没有互斥锁保护的情况下修改底层对象,从而导致内部
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
即使这个看似无辜的版本仍然无法编译!这是因为它违反了第一条规则。Rust不能静态地证明引用不会比数据库本身更有效,因为数据库是在堆上分配的,并且引用是计数的,所以可以随时删除它。但是,即使有可能以某种方式显式声明引用的生存期,但在解锁互斥锁后保留引用将允许数据竞争。根本没有办法实现
get_person()
并仍然保持线程安全

读取的线程安全实现可以选择返回数据的副本
Person
可以实现
clone()
方法,并且
get\u 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()
}
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
上字段数量的增加,这将很快变得单调乏味。它也可能会变慢,因为每次访问都必须获得一个单独的互斥锁

幸运的是,有更好的方法来实现对条目的修改。请记住,使用可变引用违反了规则,除非Rust可以证明引用不会“逃脱”正在使用的块。这可以通过反转控件来确保-我们可以引入一个
modify\u person()
,将可变引用传递给可调用的对象,而不是返回可变引用的
get\u 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))
}
最后,如果您担心
get_person()
克隆
person
的性能仅仅是为了检查它,那么创建一个不可变的
modify_person
版本是很简单的,它可以作为
get_person()
的非复制替代品:


这篇文章使用了中引用为“控制反转”的模式,只需添加sugar来演示内存数据库的另一个api

使用宏规则,可以公开db客户端api,如下所示:

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

    let person_id = 1234;

    // probably not the best design choice to duplicate the person_id,
    // for the purpose here is not important 
    db.add_person(person_id, Person::new(person_id, "Bob"));

    db_update!(db #person_id => set_name("Gambadilegno"));

    println!("your new name is {}",  db.get_person(person_id).unwrap().name);
}
我的自以为是的宏具有以下格式:

<database_instance> #<object_key> => <method_name>(<args>)
#=>()
下面是宏实现和完整演示代码:

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

macro_rules! db_update {
    ($db:ident # $id:expr => $meth:tt($($args:tt)*)) => {
        $db.modify_person($id, |person| {
            person.unwrap().$meth($($args)*);
        });
    };
}

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

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

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

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

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

    pub fn add_person(&self, id: u64, person: Person) {
        self.db.lock().unwrap().insert(id, person);
    }

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

    pub fn get_person(&self, id: u64) -> Option<Person> {
        self.db.lock().unwrap().get(&id).cloned()
    }
}
使用std::collections::HashMap;
使用std::sync::Arc;
使用std::sync::Mutex;
宏规则!数据库更新{
($db:ident#$id:expr=>$meth:tt($($args:tt)*))=>{
$db.modify|person($id,| person |{
person.unwrap().$meth($($args)*);
});
};
}
#[衍生(克隆)]
结构人{
id:u64,
名称:String,
}
默示人{
pub fn new(id:u64,name:&str)->Person{
人{
id:id,
name:name.to_string(),
}
}
fn集合\名称(&mut self,值:&str){
self.name=value.to_string();
}
}
结构数据库{
db:Arc,//从不同线程访问
}
impl数据库{
pub fn new()->数据库{
数据库{
db:Arc::new(Mutex::new(HashMap::new()),
}
}
pub fn add_person(&self,id:u64,person:person){
self.db.lock().unwrap().insert(id,person);
}
发布fn修改人(&self,id:u64,f:f)
哪里
F:FNOCE(选项),
{
f(self.db.lock().unwrap().get_mut(&id));
}
pub fn get_person(&self,id:u64)->选项{
self.db.lock()
}
}
// 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());
fn main() {
    let db = Database::new();

    let person_id = 1234;

    // probably not the best design choice to duplicate the person_id,
    // for the purpose here is not important 
    db.add_person(person_id, Person::new(person_id, "Bob"));

    db_update!(db #person_id => set_name("Gambadilegno"));

    println!("your new name is {}",  db.get_person(person_id).unwrap().name);
}
<database_instance> #<object_key> => <method_name>(<args>)
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;

macro_rules! db_update {
    ($db:ident # $id:expr => $meth:tt($($args:tt)*)) => {
        $db.modify_person($id, |person| {
            person.unwrap().$meth($($args)*);
        });
    };
}

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

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

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

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

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

    pub fn add_person(&self, id: u64, person: Person) {
        self.db.lock().unwrap().insert(id, person);
    }

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

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