diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -23,7 +23,7 @@ config: Config, // None means not known/initialized yet dirstate_parents: Cell>, - dirstate_map: RefCell>, + dirstate_map: LazyCell, } #[derive(Debug, derive_more::From)] @@ -196,7 +196,7 @@ dot_hg, config: repo_config, dirstate_parents: Cell::new(None), - dirstate_map: RefCell::new(None), + dirstate_map: LazyCell::new(Self::new_dirstate_map), }; requirements::check(&repo)?; @@ -302,24 +302,52 @@ pub fn dirstate_map( &self, ) -> Result, DirstateError> { - let mut borrowed = self.dirstate_map.borrow(); + self.dirstate_map.get_or_init(self) + } + + pub fn dirstate_map_mut( + &self, + ) -> Result, DirstateError> { + self.dirstate_map.get_mut_or_init(self) + } +} + +/// Lazily-initialized component of `Repo` with interior mutability +/// +/// This differs from `OnceCell` in that the value can still be "deinitialized" +/// later by setting its inner `Option` to `None`. +struct LazyCell { + value: RefCell>, + // `Fn`s that don’t capture environment are zero-size, so this box does + // not allocate: + init: Box Result>, +} + +impl LazyCell { + fn new(init: impl Fn(&Repo) -> Result + 'static) -> Self { + Self { + value: RefCell::new(None), + init: Box::new(init), + } + } + + fn get_or_init(&self, repo: &Repo) -> Result, E> { + let mut borrowed = self.value.borrow(); if borrowed.is_none() { drop(borrowed); // Only use `borrow_mut` if it is really needed to avoid panic in // case there is another outstanding borrow but mutation is not // needed. - *self.dirstate_map.borrow_mut() = Some(self.new_dirstate_map()?); - borrowed = self.dirstate_map.borrow() + *self.value.borrow_mut() = Some((self.init)(repo)?); + borrowed = self.value.borrow() } Ok(Ref::map(borrowed, |option| option.as_ref().unwrap())) } - pub fn dirstate_map_mut( - &self, - ) -> Result, DirstateError> { - let mut borrowed = self.dirstate_map.borrow_mut(); + pub fn get_mut_or_init(&self, repo: &Repo) -> Result, E> { + let mut borrowed = self.value.borrow_mut(); if borrowed.is_none() { - *borrowed = Some(self.new_dirstate_map()?); + *borrowed = Some((self.init)(repo)?); } Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap())) }