如何替换Mutex中的值?

Ben*_*n S 7 rust borrow-checker

我有一个隐藏在后面的Git存储库Mutex:

pub struct GitRepo {
    contents: Mutex<GitContents>,
    workdir: PathBuf,
}
Run Code Online (Sandbox Code Playgroud)

我想查询它,但最多只查询一次:在查询之后,我想只使用我们第一次得到的结果.存储库具有git2::Repository结果的向量或向量.A RepositorySend但不是Sync.

enum GitContents {
    Before { repo: git2::Repository },
    After { statuses: Git },
}

struct Git {
    statuses: Vec<(PathBuf, git2::Status)>,
}
Run Code Online (Sandbox Code Playgroud)

GitContents枚举反映了一个事实,我们要么库进行查询,或查询它的结果,但不可能兼顾.

我试图让Rust通过将存储库转换为状态的函数来强制执行此属性,因为它生成状态向量时使用存储库:

fn repo_to_statuses(repo: git2::Repository, workdir: &Path) -> Git {
    // Assume this does something useful...
    Git { statuses: Vec::new() }
}
Run Code Online (Sandbox Code Playgroud)

但是,我不能让它Mutex玩得很好.到目前为止,我尝试编写一个GitRepo使用谓词查询a的函数P,替换Mutexif中尚未查询的值:

impl GitRepo {
    fn search<P: Fn(&Git) -> bool>(&self, p: P) -> bool {
        use std::mem::replace;
        // Make this thread wait until the mutex becomes available.
        // If it's locked, it's because another thread is running repo_to_statuses
        let mut contents = self.contents.lock().unwrap();
        match *contents {
            // If the repository has been queried then just use the existing results
            GitContents::After { ref statuses } => p(statuses),
            // If it hasn't, then replace it with some results, then use them.
            GitContents::Before { ref repo } => {
                let statuses = repo_to_statuses(*repo, &self.workdir);
                let result = p(&statuses);
                replace(&mut *contents, GitContents::After { statuses });
                result
            },
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然涉及到变异,但这种方法只需要&self而不是&mut self因为它返回相同的结果,无论是第一次还是第二次查询存储库,即使第一次执行的工作量更多.但鲁斯特抱怨道:

  • 它拒绝移出repo我借来的内容repo_to_statuses(*repo, &self.workdir),即使我知道之后应立即更换价值.("不能摆脱借来的内容")
  • 它不喜欢我replace-ing &mut *contents要么,因为我一成不变借款内容的价值被match-ed.("不能借用'内容'作为可变因为它也被借用为不可变的")

有没有办法说服借阅检查员我的意图?

She*_*ter 9

你问的问题和真正的内在问题与a有任何内在联系Mutex,一旦你锁定它并有一个可变的引用或实现的类型DerefMut.

您可以使用解除引用运算符为引用分配新值*.如果您需要以前的值,则可以使用std::mem::replace.

use std::sync::Mutex;
use std::mem;

fn example_not_using_old_value(state: &Mutex<String>) {
    let mut state = state.lock().expect("Could not lock mutex");
    *state = String::from("dereferenced");
}

fn example_using_old_value(state: &Mutex<String>) -> String {
    let mut state = state.lock().expect("Could not lock mutex");
    mem::replace(&mut *state, String::from("replaced"))
}

fn main() {
    let state = Mutex::new("original".into());
    example_not_using_old_value(&state);
    let was = example_using_old_value(&state);

    println!("Is now {:?}", state);
    println!("Was {:?}", was);
}    
Run Code Online (Sandbox Code Playgroud)

我们取消引用MutexGuard<T>获取a T,并对其进行可变引用,产生一个&mut T我们可以调用mem::replace的东西.


您更广泛的问题是因为您无法摆脱借来的内容(请参阅众多问答).查看这些直接相关的问答:

您可能希望添加一个新的枚举变体,该变体表示当所有内容都已移出但尚未移回任何内容时的状态.然后,您可以将该值替换为该虚拟对象,并获取旧值的所有权,执行操作,然后将新值返回.