ic_storage/adapter/cell.rs
1//! Governed cell collection implementation.
2use super::{Bound, Memory, Storable};
3use crate::substrate::{self, DefaultMemoryImpl};
4use ic_memory::RuntimeMemory;
5use std::borrow::Cow;
6
7///
8/// `StableCell`
9///
10/// Governed upstream `Cell` with explicit initialization and read-only loading.
11///
12/// Values and stored bytes retain the upstream codec and layout contracts.
13///
14pub struct StableCell<T: Storable, M: Memory = DefaultMemoryImpl> {
15 inner: substrate::Cell<CellValue<T>, RuntimeMemory<M>>,
16}
17
18// Upstream has no load constructor. An absent value is an internal loading
19// marker, never a default T or a persisted record. Serialization refuses it
20// before upstream can grow/write on its initialization fallback. Successful
21// construction always holds Some(T), decoded exactly once by upstream.
22struct CellValue<T>(Option<T>);
23
24impl<T: Storable> Storable for CellValue<T> {
25 const BOUND: Bound = T::BOUND;
26
27 fn to_bytes(&self) -> Cow<'_, [u8]> {
28 self.0
29 .as_ref()
30 .expect("cannot initialize a loading cell")
31 .to_bytes_checked()
32 }
33
34 fn into_bytes(self) -> Vec<u8> {
35 self.0
36 .expect("cannot initialize a loading cell")
37 .into_bytes_checked()
38 }
39
40 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
41 T::check_bounds(&bytes);
42 Self(Some(T::from_bytes(bytes)))
43 }
44}
45
46impl<T: Storable, M: Memory> StableCell<T, M> {
47 /// Create a cell only in zero-page memory, enforcing declared record bounds.
48 ///
49 /// # Panics
50 /// Refuses nonempty memory or a value outside its declared bound before
51 /// writing. Growth and codec failures retain upstream behavior.
52 pub fn create(memory: RuntimeMemory<M>, value: T) -> Self {
53 assert_eq!(memory.size(), 0, "cell memory is not empty");
54 Self::reset(memory, value)
55 }
56
57 /// Deliberately replace any contents, enforcing the record's declared bound.
58 ///
59 /// # Panics
60 /// Refuses oversized/fixed-width mismatches before writing. Growth and codec
61 /// failures retain upstream behavior. Unbounded record types remain allowed.
62 pub fn reset(memory: RuntimeMemory<M>, value: T) -> Self {
63 Self {
64 inner: substrate::Cell::new(memory, CellValue(Some(value))),
65 }
66 }
67 /// Load an existing cell without initializing, writing or growing memory.
68 ///
69 /// Requires no default value or `Default`/`Clone` bound. The retained value
70 /// is decoded by upstream using `T::from_bytes`. A successful load retains
71 /// the allocation's exclusive lease until this cell is dropped.
72 ///
73 /// # Errors
74 /// Returns [`ic_memory::StableCellPayloadError`] for empty/foreign memory,
75 /// unsupported layout versions, a payload beyond memory or the declared maximum. The
76 /// consumed lease is released on failure; no bytes are changed.
77 ///
78 /// # Panics
79 /// Codec and backing failures propagate as upstream panics. Loading does
80 /// not write or grow even on these failures. Ordinary unwinding releases
81 /// the consumed lease; Wasm traps retain the host's rollback semantics.
82 /// This validates the envelope, not the application's schema or recovery policy.
83 pub fn load(memory: RuntimeMemory<M>) -> Result<Self, ic_memory::StableCellPayloadError> {
84 let maximum = match T::BOUND {
85 Bound::Bounded { max_size, .. } => max_size as usize,
86 Bound::Unbounded => usize::MAX,
87 };
88 ic_memory::stable_cell_payload_length(&memory, maximum)?;
89 Ok(Self {
90 inner: substrate::Cell::init(memory, CellValue(None)),
91 })
92 }
93 /// Delegates to upstream `Cell::get` with the governed allocation.
94 #[expect(
95 clippy::missing_panics_doc,
96 reason = "constructors always establish a cached value"
97 )]
98 pub fn get(&self) -> &T {
99 self.inner.get().0.as_ref().expect("loaded cell value")
100 }
101 /// Delegates to upstream `Cell::set` with the governed allocation.
102 ///
103 /// # Panics
104 /// Panics on insufficient memory or codec/backing failure, as upstream.
105 pub fn set(&mut self, value: T) -> T {
106 self.inner
107 .set(CellValue(Some(value)))
108 .0
109 .expect("loaded cell value")
110 }
111}