use core::cell::{Ref, RefCell, RefMut};
pub struct InMemory<C> {
value: RefCell<C>,
}
unsafe impl<C> Sync for InMemory<C> {}
impl<C> InMemory<C> {
pub const fn new(value: C) -> Self {
Self {
value: RefCell::new(value),
}
}
pub fn with<R>(&self, f: impl FnOnce(&mut C) -> R) -> crate::Result<R> {
let mut value = self.try_borrow_mut()?;
Ok(f(&mut value))
}
pub fn try_borrow(&self) -> crate::Result<Ref<'_, C>> {
self.value
.try_borrow()
.map_err(|_| "In-memory value was already mutably borrowed. Do not use re-entrantly.")
}
pub fn try_borrow_mut(&self) -> crate::Result<RefMut<'_, C>> {
self.value
.try_borrow_mut()
.map_err(|_| "In-memory value was already borrowed. Do not use re-entrantly.")
}
}
impl<T> InMemory<Option<T>> {
pub const fn empty() -> Self {
Self::new(None)
}
}
impl<T> InMemory<Option<T>>
where
T: Default,
{
pub fn upsert_with<R>(&self, f: impl FnOnce(&mut T) -> R) -> crate::Result<R> {
let mut slot = self.try_borrow_mut()?;
Ok(f(slot.get_or_insert_default()))
}
}
#[cfg(test)]
mod tests {
use super::InMemory;
#[derive(Default)]
struct Handle {
id: u32,
retries: u8,
}
#[test]
fn present_value_needs_no_option() {
static CTX: InMemory<u32> = InMemory::new(7);
let doubled: u32 = CTX.with(|value| *value * 2).unwrap();
assert_eq!(doubled, 14);
}
#[test]
fn present_value_updates_individual_fields() {
static CTX: InMemory<Handle> = InMemory::new(Handle { id: 1, retries: 0 });
CTX.with(|handle| handle.retries += 1).unwrap();
CTX.with(|handle| handle.id = 9).unwrap();
let fields = CTX.with(|handle| (handle.id, handle.retries)).unwrap();
assert_eq!(fields, (9, 1));
}
#[test]
fn empty_slot_sets_and_takes() {
static CTX: InMemory<Option<Handle>> = InMemory::empty();
assert!(CTX.with(|slot| slot.is_none()).unwrap());
CTX.with(|slot| *slot = Some(Handle { id: 3, retries: 0 }))
.unwrap();
let taken: Option<Handle> = CTX.with(Option::take).unwrap();
assert_eq!(taken.map(|handle| handle.id), Some(3));
assert!(CTX.with(|slot| slot.is_none()).unwrap());
}
#[test]
fn empty_slot_upserts_individual_fields() {
static CTX: InMemory<Option<Handle>> = InMemory::empty();
CTX.upsert_with(|handle| handle.id = 7).unwrap();
let retries = CTX
.upsert_with(|handle| {
handle.retries += 1;
handle.retries
})
.unwrap();
assert_eq!(retries, 1);
CTX.with(|slot| {
if let Some(handle) = slot {
handle.retries += 1;
}
})
.unwrap();
let fields = CTX
.with(|slot| slot.as_ref().map(|handle| (handle.id, handle.retries)))
.unwrap();
assert_eq!(fields, Some((7, 2)));
}
#[test]
fn reentrant_access_is_rejected() {
static CTX: InMemory<u32> = InMemory::new(7);
assert!(CTX.with(|_| CTX.with(|_| ())).unwrap().is_err());
}
}