如何替换 Mutex 中的值?
How can I replace the value inside a Mutex?
我有一个 Git 存储库隐藏在 Mutex
:
后面
pub struct GitRepo {
contents: Mutex<GitContents>,
workdir: PathBuf,
}
我想查询,但最多只能查询一次:查询完后,我只想使用我们第一次得到的结果。存储库具有 git2::Repository
或结果向量。 Repository
是 Send
但不是 Sync
.
enum GitContents {
Before { repo: git2::Repository },
After { statuses: Git },
}
struct Git {
statuses: Vec<(PathBuf, git2::Status)>,
}
GitContents
枚举反映了这样一个事实,即我们要么拥有要查询的存储库,要么拥有查询结果,但绝不会两者兼而有之。
我试图让 Rust 强制执行此 属性 通过具有将存储库转换为状态的功能 consume 存储库,因为它产生状态向量:
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
因为它 returns 相同的结果,无论是第一次还是第二次查询存储库时间,即使有更多的工作要做。但是 Rust 抱怨:
- 它拒绝将
repo
从我在 repo_to_statuses(*repo, &self.workdir)
中借用的内容中移出,即使我知道该值应该在之后立即被替换。 ("cannot move out of borrowed content")
- 它也不喜欢我
replace
-ing &mut *contents
,因为我在 match
-ed 中不变地借用内容。 ("cannot borrow 'contents' as mutable because it is also borrowed as immutable")
有没有办法让借阅检查员相信我的意图?
你问的问题和真正的内部问题在本质上与 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
。
您的更广泛的问题是因为您无法移出借用的内容(请参阅 numerous Q&A for that)。查看这些直接相关的问答:
- Temporarily move out of borrowed content
您可能希望添加一个新的枚举变体,它表示所有内容都已移出但尚未移回任何内容时的状态。然后您可以用该虚拟对象替换您的值并获得旧值的所有权,执行您的操作,然后将新值放回原处。
我有一个 Git 存储库隐藏在 Mutex
:
pub struct GitRepo {
contents: Mutex<GitContents>,
workdir: PathBuf,
}
我想查询,但最多只能查询一次:查询完后,我只想使用我们第一次得到的结果。存储库具有 git2::Repository
或结果向量。 Repository
是 Send
但不是 Sync
.
enum GitContents {
Before { repo: git2::Repository },
After { statuses: Git },
}
struct Git {
statuses: Vec<(PathBuf, git2::Status)>,
}
GitContents
枚举反映了这样一个事实,即我们要么拥有要查询的存储库,要么拥有查询结果,但绝不会两者兼而有之。
我试图让 Rust 强制执行此 属性 通过具有将存储库转换为状态的功能 consume 存储库,因为它产生状态向量:
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
因为它 returns 相同的结果,无论是第一次还是第二次查询存储库时间,即使有更多的工作要做。但是 Rust 抱怨:
- 它拒绝将
repo
从我在repo_to_statuses(*repo, &self.workdir)
中借用的内容中移出,即使我知道该值应该在之后立即被替换。 ("cannot move out of borrowed content") - 它也不喜欢我
replace
-ing&mut *contents
,因为我在match
-ed 中不变地借用内容。 ("cannot borrow 'contents' as mutable because it is also borrowed as immutable")
有没有办法让借阅检查员相信我的意图?
你问的问题和真正的内部问题在本质上与 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
。
您的更广泛的问题是因为您无法移出借用的内容(请参阅 numerous Q&A for that)。查看这些直接相关的问答:
- Temporarily move out of borrowed content
您可能希望添加一个新的枚举变体,它表示所有内容都已移出但尚未移回任何内容时的状态。然后您可以用该虚拟对象替换您的值并获得旧值的所有权,执行您的操作,然后将新值放回原处。