I have a Git repository hidden behind a Mutex
:
pub struct GitRepo {
contents: Mutex<GitContents>,
workdir: PathBuf,
}
I want to query it, but only a maximum of once: after it's been queried, I want to just use the results we got the first time. A repository has either a git2::Repository
, or a vector of results. A Repository
is Send
but not Sync
.
enum GitContents {
Before { repo: git2::Repository },
After { statuses: Git },
}
struct Git {
statuses: Vec<(PathBuf, git2::Status)>,
}
The GitContents
enum reflects the fact that we either have the repository to query, or the results of querying it, but never both.
I'm trying to get Rust to enforce this property by having the function that turns a repository into statuses consume the repository as it produces the status vector:
fn repo_to_statuses(repo: git2::Repository, workdir: &Path) -> Git {
// Assume this does something useful...
Git { statuses: Vec::new() }
}
However, I can't get the Mutex
to play nice with this. Here is my attempt so far to write a function that queries a GitRepo
with a predicate P
, replacing the value inside the Mutex
if it hasn't been queried yet:
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
},
}
}
}
Although there is mutation involved, this method only takes &self
rather than &mut self
because it returns the same result regardless of whether the repository is being queried for the first or second time, even though there's more work being done on the first. But Rust complains:
- It refuses to move the
repo
out of the contents I've borrowed inrepo_to_statuses(*repo, &self.workdir)
, even though I know the value should get replaced immediately afterwards. ("cannot move out of borrowed content") - It doesn't like me
replace
-ing&mut *contents
either, because I'm borrowing the contents immutably as the value beingmatch
-ed. ("cannot borrow 'contents' as mutable because it is also borrowed as immutable")
Is there any way to convince the borrow checker of my intentions?