如何替换互斥锁中的值?

时间:2017-08-31 16:05:50

标签: rust borrow-checker

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

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)>,
}

GitContents枚举反映了这样一个事实,即我们要么拥有要查询的存储库,要么查询它的结果,但从来没有。

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

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

然而,我无法让Mutex对此发挥得很好。到目前为止,我尝试编写一个函数,使用谓词GitRepo查询P,如果尚未查询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而不是repo,因为无论是第一次还是第二次查询存储库,它都返回相同的结果,即使有更多工作正在进行中。但鲁斯特抱怨道:

  • 它拒绝将repo_to_statuses(*repo, &self.workdir)移出我在replace中借用的内容,即使我知道该值应该在之后立即被替换。 (“不能摆脱借来的内容”)
  • 它也不喜欢我&mut *contents - 正在match,因为我正在以不可变的方式借用内容,因为值为(defmacro setq-local (var val) (list 'set (list 'make-local-variable (list 'quote var)) val)) (defun my-beginning-of-line () (interactive) (message "entering my-beginning-of-line: transient-mark-mode = %s" transient-mark-mode) ; Straight from "handle-shift-selection" in emacs 25.2 (unless (and mark-active (eq (car-safe transient-mark-mode) 'only)) (setq-local transient-mark-mode (cons 'only (unless (eq transient-mark-mode 'lambda) transient-mark-mode))) (push-mark nil nil t)) (beginning-of-line) (message "exiting my-beginning-of-line: transient-mark-mode = %s\n" transient-mark-mode)) (global-set-key [home] 'my-beginning-of-line) - ed。 (“不能借用'内容'作为可变因为它也被借用为不可变的”)

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

1 个答案:

答案 0 :(得分:6)

你问的问题和真正的内在问题与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);
}    

我们取消引用MutexGuard<T>以获取T,并对其进行可变引用,产生&mut T,我们可以将mem::replace称为{{1}}。

您更广泛的问题是因为您无法摆脱借来的内容(请参阅numerous Q&A for that)。请参阅这些直接相关的Q&amp; A:

您可能希望添加一个新的枚举变体,该变体代表当所有内容都已移出但尚未移回任何内容时的状态。然后,您可以使用该虚拟替换您的值并获取旧值的所有权,执行操作,然后重新放置新值。