rust 借用检查器非常小心,“”是一个引用,因此它所引用的数据不能作为可变数据借用

vltsax25  于 2023-02-12  发布在  其他
关注(0)|答案(1)|浏览(165)

我正在和Rust一起工作,我正在与某种情况作斗争,以克服编译器借用检查器。
我有一个需要实现Struct的情况。在实现的初始化过程中,我需要在它完全初始化之前将对Struct的引用Repo传递给另一个实现。
第二个结构Backend实现与数据库的连接。连接完成后,需要从数据库中提取信息,并填充第一个结构的字段。在此过程中,需要使用数据库中的信息更新Backend结构
这是一个MUC。

use std::{collections::HashMap, borrow::BorrowMut};
use serde_json::Value;

#[derive(Debug)]
pub enum BackendError {
    Generic(String),
}

impl From<sqlite::Error> for BackendError{
    fn from(error: sqlite::Error) -> Self{
        BackendError::Generic(error.to_string())
    }
}

pub struct Entry {
    pub version: String,
    pub val: Value,
}

// Information needed by the repository functionality
pub struct Repo{
    pub values: HashMap<String,HashMap<String, Entry>>,
    backend: Box<dyn Backend>
}

impl Repo {
    pub fn new() -> Repo{

        // initialize Persistence backend and load any prior status_values based on backend selection
        let inst = init_backend_provider();

        let repo = Repo {
            values: HashMap::new(),
            backend: inst ,
        };
        
        match repo.backend.load(&repo){

            Ok(s) => s,
            Err(e) => log::error!("{:?}", e),
        }

        repo
    }
}

// ---------------------------------------------------

pub trait Backend: Send + Sync{
    fn load(&self, repo: &Repo) -> Result<(), BackendError>; 
}

pub fn init_backend_provider() -> Box<dyn Backend>{

    Box::new(DB::new())
}

pub struct DB{}

impl DB{

    fn new () -> Self{
        DB{}
    }
}

impl Backend for DB{

    fn load(&self, repo: &Repo) -> Result<(), BackendError>{

        // let key_value = repo.values.entry("key".to_string()).or_insert(HashMap::new());

        Ok(())
    }
}

fn main(){

    let repo = Repo::new();
}

我得到的错误是显而易见的。如果它是一个引用,我就不能借用可变的数据。
我试图将其作为一个可变引用& mut.... match repo. backend. load(& mut repo)...这会导致编译器抱怨我试图从一个已经是不可变的借位中作为可变借位。

let mut repo = Repo {
            values: HashMap::new(),
            backend: inst ,
        };
        
match repo.backend.load(&mut repo)

说来话长,我尝试了多种组合来使repo可变,但没有成功。
我想做的是
当我调用load()时,我希望传递Struct引用。这样我就可以查看哈希MapValue。我希望查看第一级条目,或者创建一个新条目,或者拉取该条目并更新它。在这种情况下,它总是需要作为初始化过程的一部分来创建。一旦我创建了该条目,我将调用DB并请求一个特定表的所有可用字段。这些都可以工作,所以我不会用可以工作的代码重载MUC。

gajydyqb

gajydyqb1#

我认为你应该问自己的问题不是“我如何使它可变?”而是“我应该如何真正设计我的结构体和接口?"。
你现在的API意味着初始化一个Backend需要......它本身?这完全是无稽之谈。
为什么这么说呢?因为Backend::load需要一个&Repo,它包含一个Backend,所以load把它自己作为一个参数。
rust 借检查员抱怨这一点是正确的;你对它抱怨的解释是错误的。2你的问题不是易变性,而是你的API没有意义。
你实际上应该做的是:不要给予Backend::init一个自身的引用。它只需要values,所以给它一个引用。然后它就工作了:

use serde_json::Value;
use std::collections::HashMap;

#[derive(Debug)]
pub enum BackendError {
    Generic(String),
}

impl From<sqlite::Error> for BackendError {
    fn from(error: sqlite::Error) -> Self {
        BackendError::Generic(error.to_string())
    }
}

pub struct Entry {
    pub version: String,
    pub val: Value,
}

// Information needed by the repository functionality
pub struct Repo {
    pub values: HashMap<String, HashMap<String, Entry>>,
    backend: Box<dyn Backend>,
}

impl Repo {
    pub fn new() -> Repo {
        // initialize Persistence backend and load any prior status_values based on backend selection
        let inst = init_backend_provider();

        let mut repo = Repo {
            values: HashMap::new(),
            backend: inst,
        };

        match repo.backend.load(&mut repo.values) {
            Ok(s) => s,
            Err(e) => log::error!("{:?}", e),
        }

        repo
    }
}

// ---------------------------------------------------

pub trait Backend: Send + Sync {
    fn load(
        &self,
        values: &mut HashMap<String, HashMap<String, Entry>>,
    ) -> Result<(), BackendError>;
}

pub fn init_backend_provider() -> Box<dyn Backend> {
    Box::new(DB::new())
}

pub struct DB {}

impl DB {
    fn new() -> Self {
        DB {}
    }
}

impl Backend for DB {
    fn load(
        &self,
        values: &mut HashMap<String, HashMap<String, Entry>>,
    ) -> Result<(), BackendError> {
        let key_value = values.entry("key".to_string()).or_insert(HashMap::new());

        Ok(())
    }
}

fn main() {
    let repo = Repo::new();
}

当然还有很多其他的架构方法来解决这个自引用API,基本的问题是如果Repo需要一个Backend,但是Backend也需要一个Repo,那么你的依赖树就不是一棵树,而是一个圆,你应该问自己:谁真正依赖于什么?然后构建一个结构布局,其依赖关系实际上是一棵树。

相关问题