ok/jj
1
0
Fork 0
forked from mirrors/jj

repo: reimplement DirtyCell without using unsafe

While the safe implementation is a bit more complex (and probably more branchy),
I don't think the runtime overhead would matter here. Let's remove one more
unsafe for better code maintainability.
This commit is contained in:
Yuya Nishihara 2023-11-08 18:25:22 +09:00
parent 57d7aeab5e
commit 6ff3a4f3df

View file

@ -1340,53 +1340,67 @@ pub enum CheckOutCommitError {
} }
mod dirty_cell { mod dirty_cell {
use std::cell::{Cell, RefCell}; use std::cell::{OnceCell, RefCell};
/// Cell that lazily updates the value after `mark_dirty()`. /// Cell that lazily updates the value after `mark_dirty()`.
///
/// A clean value can be immutably borrowed within the `self` lifetime.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DirtyCell<T> { pub struct DirtyCell<T> {
value: RefCell<T>, // Either clean or dirty value is set. The value is boxed to reduce stack space
dirty: Cell<bool>, // and memcopy overhead.
clean: OnceCell<Box<T>>,
dirty: RefCell<Option<Box<T>>>,
} }
impl<T> DirtyCell<T> { impl<T> DirtyCell<T> {
pub fn with_clean(value: T) -> Self { pub fn with_clean(value: T) -> Self {
DirtyCell { DirtyCell {
value: RefCell::new(value), clean: OnceCell::from(Box::new(value)),
dirty: Cell::new(false), dirty: RefCell::new(None),
} }
} }
pub fn get_or_ensure_clean(&self, f: impl FnOnce(&mut T)) -> &T { pub fn get_or_ensure_clean(&self, f: impl FnOnce(&mut T)) -> &T {
// SAFETY: get_mut/mark_dirty(&mut self) should invalidate any previously-clean self.clean.get_or_init(|| {
// references leaked by this method. Clean value never changes until then. // Panics if ensure_clean() is invoked from with_ref() callback for example.
self.ensure_clean(f); let mut value = self.dirty.borrow_mut().take().unwrap();
unsafe { &*self.value.as_ptr() } f(&mut value);
value
})
} }
pub fn ensure_clean(&self, f: impl FnOnce(&mut T)) { pub fn ensure_clean(&self, f: impl FnOnce(&mut T)) {
if self.dirty.get() { self.get_or_ensure_clean(f);
// This borrow_mut() ensures that there is no dirty temporary reference.
// Panics if ensure_clean() is invoked from with_ref() callback for example.
f(&mut self.value.borrow_mut());
self.dirty.set(false);
}
} }
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.value.into_inner() *self
.clean
.into_inner()
.or_else(|| self.dirty.into_inner())
.unwrap()
} }
pub fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R { pub fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R {
f(&self.value.borrow()) if let Some(value) = self.clean.get() {
f(value)
} else {
f(self.dirty.borrow().as_ref().unwrap())
}
} }
pub fn get_mut(&mut self) -> &mut T { pub fn get_mut(&mut self) -> &mut T {
self.value.get_mut() self.clean
.get_mut()
.or_else(|| self.dirty.get_mut().as_mut())
.unwrap()
} }
pub fn mark_dirty(&mut self) { pub fn mark_dirty(&mut self) {
*self.dirty.get_mut() = true; if let Some(value) = self.clean.take() {
*self.dirty.get_mut() = Some(value);
}
} }
} }
} }