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()
32 }
33
34 fn into_bytes(self) -> Vec<u8> {
35 self.0
36 .expect("cannot initialize a loading cell")
37 .into_bytes()
38 }
39
40 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
41 Self(Some(T::from_bytes(bytes)))
42 }
43}
44
45impl<T: Storable, M: Memory> StableCell<T, M> {
46 /// Create a new collection, replacing any prior collection in the selected memory.
47 /// This is destructive even when a valid collection already exists.
48 ///
49 /// # Panics
50 /// Panics on insufficient memory or codec/backing failure.
51 pub fn new(memory: RuntimeMemory<M>, value: T) -> Self {
52 Self {
53 inner: substrate::Cell::new(memory, CellValue(Some(value))),
54 }
55 }
56 /// Open a recognized retained collection; initialize a new one when memory
57 /// is empty or has foreign magic. Foreign contents may be overwritten.
58 /// This is not a corruption-recovery or schema-admission operation.
59 ///
60 /// # Panics
61 /// Panics for unsupported recognized layouts, invalid retained contents,
62 /// insufficient memory or codec/backing failure.
63 pub fn init(memory: RuntimeMemory<M>, default_value: T) -> Self {
64 Self {
65 inner: substrate::Cell::init(memory, CellValue(Some(default_value))),
66 }
67 }
68 /// Load an existing cell without initializing, writing or growing memory.
69 ///
70 /// Requires no default value or `Default`/`Clone` bound. The retained value
71 /// is decoded by upstream using `T::from_bytes`. A successful load retains
72 /// the allocation's exclusive lease until this cell is dropped.
73 ///
74 /// # Errors
75 /// Returns [`ic_memory::StableCellPayloadError`] for empty/foreign memory,
76 /// unsupported layout versions or a payload extending beyond memory. The
77 /// consumed lease is released on failure; no bytes are changed.
78 ///
79 /// # Panics
80 /// Codec and backing failures propagate as upstream panics. Loading does
81 /// not write or grow even on these failures. Ordinary unwinding releases
82 /// the consumed lease; Wasm traps retain the host's rollback semantics.
83 /// This validates the envelope, not the application's schema or recovery policy.
84 pub fn load(memory: RuntimeMemory<M>) -> Result<Self, ic_memory::StableCellPayloadError> {
85 ic_memory::stable_cell_payload_length(&memory, usize::MAX)?;
86 Ok(Self {
87 inner: substrate::Cell::init(memory, CellValue(None)),
88 })
89 }
90 /// Delegates to upstream `Cell::get` with the governed allocation.
91 #[expect(
92 clippy::missing_panics_doc,
93 reason = "constructors always establish a cached value"
94 )]
95 pub fn get(&self) -> &T {
96 self.inner.get().0.as_ref().expect("loaded cell value")
97 }
98 /// Delegates to upstream `Cell::set` with the governed allocation.
99 ///
100 /// # Panics
101 /// Panics on insufficient memory or codec/backing failure, as upstream.
102 pub fn set(&mut self, value: T) -> T {
103 self.inner
104 .set(CellValue(Some(value)))
105 .0
106 .expect("loaded cell value")
107 }
108}
109
110/// A stable cell whose writes enforce the record's declared `Storable` bound.
111pub struct BoundedCell<T: Storable, M: Memory = DefaultMemoryImpl> {
112 cell: substrate::Cell<BoundedValue<T>, RuntimeMemory<M>>,
113}
114
115impl<T: Storable, M: Memory> BoundedCell<T, M> {
116 /// Open a current cell or initialize a fresh bounded record.
117 /// Empty memory or foreign magic initializes the supplied value, potentially
118 /// overwriting foreign contents. Recognized incompatible layouts are refused.
119 ///
120 /// # Panics
121 /// Panics for unbounded records or encodings that violate the declared bound.
122 /// Also retains [`StableCell::init`] layout, growth and codec/backing panics.
123 #[must_use]
124 pub fn init(memory: RuntimeMemory<M>, value: T) -> Self {
125 assert!(matches!(T::BOUND, Bound::Bounded { .. }));
126 Self {
127 cell: substrate::Cell::init(memory, BoundedValue(value)),
128 }
129 }
130
131 /// Read the cached current record without serializing or cloning it.
132 #[must_use]
133 pub fn get(&self) -> &T {
134 &self.cell.get().0
135 }
136
137 /// Validate and persist a complete replacement, returning the prior record.
138 ///
139 /// # Panics
140 /// Panics before writing if the encoding violates its declared bound.
141 pub fn set(&mut self, value: T) -> T {
142 self.cell.set(BoundedValue(value)).0
143 }
144}
145
146struct BoundedValue<T>(T);
147
148impl<T: Storable> Storable for BoundedValue<T> {
149 const BOUND: Bound = T::BOUND;
150
151 fn to_bytes(&self) -> Cow<'_, [u8]> {
152 self.0.to_bytes_checked()
153 }
154
155 fn into_bytes(self) -> Vec<u8> {
156 self.0.into_bytes_checked()
157 }
158
159 fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
160 if matches!(T::BOUND, Bound::Unbounded) {
161 unreachable!("bounded cell record must declare a bound");
162 }
163 T::check_bounds(&bytes);
164 Self(T::from_bytes(bytes))
165 }
166}