如何替换 Mutex 内的值?

22

我有一个Git仓库,它被隐藏在一个Mutex后面:

pub struct GitRepo {
    contents: Mutex<GitContents>,
    workdir: PathBuf,
}

我想查询它,但只能最多查询一次:在查询过后,我希望只使用我们第一次得到的结果。一个存储库有一个git2::Repository,或者是一组结果的向量。一个RepositorySend但不是Sync

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

struct Git {
    statuses: Vec<(PathBuf, git2::Status)>,
}
< p > GitContents 枚举反映了我们要么有可查询的存储库,要么有查询其结果,但两者不可能同时存在。

我试图通过使将存储库转换为状态的函数在产生状态向量时“消耗”存储库来强制执行此属性:

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

然而,我无法让 Mutex 与此合作。这是我迄今为止尝试编写的使用谓词P查询 GitRepo 并在尚未查询过值的情况下替换 Mutex 内部值的函数:
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
            },
        }
    }
}

尽管涉及到变异,但是该方法仅采用&self而非&mut self,因为它返回相同的结果,无论是第一次还是第二次查询存储库,即使在第一次查询时有更多的工作要做。但是Rust会抱怨:
  • 它拒绝将repo移出我在repo_to_statuses(*repo, &self.workdir)中借用的内容,即使我知道该值应立即被替换。(“cannot move out of borrowed content”)
  • 它也不喜欢我对&mut *contents进行replace,因为我将内容以不可变的方式借用作为match的值。(“cannot borrow 'contents' as mutable because it is also borrowed as immutable”)

有没有办法说服borrow checker我的意图?

1个回答

28
你提出的问题和实际存在的内部问题与 Mutex 没有本质关联。一旦你锁定了它,并拥有了可变引用或实现了 DerefMut 的类型,就可以使用解引用运算符 * 给引用赋新值。如果需要以前的值,可以使用 std::mem::replace。请注意保留原文中的 HTML 标签。
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);
}    

我们取消引用MutexGuard<T>以获取一个T,并取得对该值的可变引用,从而获得一个&mut T,然后我们可以使用mem::replace方法。
您面临的更广泛问题是无法从借用内容中移出(请参见许多相关Q&A)。请参考以下直接相关的Q&A: 您可以添加一个新的枚举变体,表示所有东西都已被移出,但尚未移回。 然后,您可以使用该虚拟枚举值替换您的值,并拥有旧值,执行操作,然后将新值放回。

你可能希望添加一个新的枚举变量,表示所有东西都已经移出,但还没有任何东西被移回来的状态。这正是我需要听到的部分。谢谢! - Ben S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接