changeset 47958:fc208d6faed3

rust: Move lazy initialization of `Repo::dirstate_map` into a generic struct More components of `Repo` will be added following the same pattern. Differential Revision: https://phab.mercurial-scm.org/D11405
author Simon Sapin <simon.sapin@octobus.net>
date Mon, 13 Sep 2021 13:16:10 +0200
parents d44740725b95
children 21d25e9ee58e
files rust/hg-core/src/repo.rs
diffstat 1 files changed, 38 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-core/src/repo.rs	Mon Sep 13 13:01:25 2021 +0200
+++ b/rust/hg-core/src/repo.rs	Mon Sep 13 13:16:10 2021 +0200
@@ -23,7 +23,7 @@
     config: Config,
     // None means not known/initialized yet
     dirstate_parents: Cell<Option<DirstateParents>>,
-    dirstate_map: RefCell<Option<OwningDirstateMap>>,
+    dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
 }
 
 #[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<Ref<OwningDirstateMap>, DirstateError> {
-        let mut borrowed = self.dirstate_map.borrow();
+        self.dirstate_map.get_or_init(self)
+    }
+
+    pub fn dirstate_map_mut(
+        &self,
+    ) -> Result<RefMut<OwningDirstateMap>, 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<T, E> {
+    value: RefCell<Option<T>>,
+    // `Fn`s that don’t capture environment are zero-size, so this box does
+    // not allocate:
+    init: Box<dyn Fn(&Repo) -> Result<T, E>>,
+}
+
+impl<T, E> LazyCell<T, E> {
+    fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
+        Self {
+            value: RefCell::new(None),
+            init: Box::new(init),
+        }
+    }
+
+    fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, 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<RefMut<OwningDirstateMap>, DirstateError> {
-        let mut borrowed = self.dirstate_map.borrow_mut();
+    pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, 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()))
     }