【问题标题】:How can I replace the value inside a Mutex?如何替换 Mutex 中的值?
【发布时间】:2018-02-09 16:02:06
【问题描述】:

我有一个隐藏在 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 很好地处理这个问题。到目前为止,我尝试编写一个函数,用谓词 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
            },
        }
    }
}

虽然涉及到突变,但此方法只使用&amp;self 而不是&amp;mut self,因为无论是第一次还是第二次查询存储库,它都会返回相同的结果,即使还有更多工作要做首先。但 Rust 抱怨:

  • 它拒绝将repo 移出我在repo_to_statuses(*repo, &amp;self.workdir) 中借用的内容,即使我知道该值应该在之后立即被替换。 (“不能移出借来的内容”)
  • 它也不喜欢我replace-ing &amp;mut *contents,因为我以match-ed 的值不变地借用内容。 (“不能将‘内容’借用为可变的,因为它也被借用为不可变的”)

有什么方法可以让借阅检查员相信我的意图?

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    一旦你锁定了Mutex,并拥有一个可变引用或实现DerefMut 的类型,你所问的问题和真正的内在问题就与Mutex 无关。

    您可以使用取消引用运算符* 为引用分配新值。如果需要之前的值,可以使用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&lt;T&gt; 的引用以获得T,并对其进行可变引用,从而产生一个&amp;mut T,我们可以将其称为mem::replace


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

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

    【讨论】:

    • “您可能希望添加一个新的枚举变量,它表示所有内容都已移出但尚未移回任何内容时的状态。”这是我需要听到的部分。谢谢!
    猜你喜欢
    • 2017-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-04
    • 2015-11-14
    • 2014-02-01
    • 2021-08-10
    • 1970-01-01
    相关资源
    最近更新 更多