use super::{Bound, Memory, Storable};
use crate::substrate::{self, DefaultMemoryImpl};
use ic_memory::RuntimeMemory;
use std::borrow::Cow;
pub struct StableCell<T: Storable, M: Memory = DefaultMemoryImpl> {
inner: substrate::Cell<CellValue<T>, RuntimeMemory<M>>,
}
struct CellValue<T>(Option<T>);
impl<T: Storable> Storable for CellValue<T> {
const BOUND: Bound = T::BOUND;
fn to_bytes(&self) -> Cow<'_, [u8]> {
self.0
.as_ref()
.expect("cannot initialize a loading cell")
.to_bytes()
}
fn into_bytes(self) -> Vec<u8> {
self.0
.expect("cannot initialize a loading cell")
.into_bytes()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
Self(Some(T::from_bytes(bytes)))
}
}
impl<T: Storable, M: Memory> StableCell<T, M> {
pub fn new(memory: RuntimeMemory<M>, value: T) -> Self {
Self {
inner: substrate::Cell::new(memory, CellValue(Some(value))),
}
}
pub fn init(memory: RuntimeMemory<M>, default_value: T) -> Self {
Self {
inner: substrate::Cell::init(memory, CellValue(Some(default_value))),
}
}
pub fn load(memory: RuntimeMemory<M>) -> Result<Self, ic_memory::StableCellPayloadError> {
ic_memory::stable_cell_payload_length(&memory, usize::MAX)?;
Ok(Self {
inner: substrate::Cell::init(memory, CellValue(None)),
})
}
#[expect(
clippy::missing_panics_doc,
reason = "constructors always establish a cached value"
)]
pub fn get(&self) -> &T {
self.inner.get().0.as_ref().expect("loaded cell value")
}
pub fn set(&mut self, value: T) -> T {
self.inner
.set(CellValue(Some(value)))
.0
.expect("loaded cell value")
}
}
pub struct BoundedCell<T: Storable, M: Memory = DefaultMemoryImpl> {
cell: substrate::Cell<BoundedValue<T>, RuntimeMemory<M>>,
}
impl<T: Storable, M: Memory> BoundedCell<T, M> {
#[must_use]
pub fn init(memory: RuntimeMemory<M>, value: T) -> Self {
assert!(matches!(T::BOUND, Bound::Bounded { .. }));
Self {
cell: substrate::Cell::init(memory, BoundedValue(value)),
}
}
#[must_use]
pub fn get(&self) -> &T {
&self.cell.get().0
}
pub fn set(&mut self, value: T) -> T {
self.cell.set(BoundedValue(value)).0
}
}
struct BoundedValue<T>(T);
impl<T: Storable> Storable for BoundedValue<T> {
const BOUND: Bound = T::BOUND;
fn to_bytes(&self) -> Cow<'_, [u8]> {
self.0.to_bytes_checked()
}
fn into_bytes(self) -> Vec<u8> {
self.0.into_bytes_checked()
}
fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
if matches!(T::BOUND, Bound::Unbounded) {
unreachable!("bounded cell record must declare a bound");
}
T::check_bounds(&bytes);
Self(T::from_bytes(bytes))
}
}