ic_stable_structures/cell.rs
1//! A serializable value stored in the stable memory.
2use crate::storable::Storable;
3use crate::{read_to_vec, Memory, WASM_PAGE_SIZE};
4use std::borrow::{Borrow, Cow};
5use std::fmt;
6
7#[cfg(test)]
8mod tests;
9
10const MAGIC: &[u8; 3] = b"SCL"; // short for "stable cell"
11const HEADER_V1_SIZE: u64 = 8;
12const LAYOUT_VERSION: u8 = 1;
13
14// NOTE: the size of this structure should be equal to [HEADER_V1_SIZE].
15// NOTE: if you have to add more fields, you need to increase the version and handle decoding of
16// previous versions in `Cell::read_header`.
17//
18// # V1 layout
19//
20// -------------------------------
21// Magic "SCL" ↕ 3 bytes
22// -------------------------------
23// Layout version ↕ 1 byte
24// -------------------------------
25// Value length = N ↕ 4 bytes
26// -------------------------------
27// <encoded value> ↕ N bytes
28// -------------------------------
29#[derive(Debug)]
30struct HeaderV1 {
31 magic: [u8; 3],
32 version: u8,
33 value_length: u32,
34}
35
36/// Indicates a failure to initialize a Cell.
37#[derive(Debug, PartialEq, Eq)]
38pub enum InitError {
39 /// The version of the library does not support version of the cell layout encoded in the
40 /// memory.
41 IncompatibleVersion {
42 last_supported_version: u8,
43 decoded_version: u8,
44 },
45 /// The initial value was to large to fit into the memory.
46 ValueTooLarge { value_size: u64 },
47}
48
49impl fmt::Display for InitError {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 InitError::IncompatibleVersion {
53 last_supported_version,
54 decoded_version,
55 } => write!(
56 f,
57 "Incompatible version: last supported version is {}, but the memory contains version {}",
58 last_supported_version, decoded_version
59 ),
60 InitError::ValueTooLarge { value_size } => write!(
61 f,
62 "The initial value is too large to fit into the memory: {} bytes",
63 value_size
64 ),
65 }
66 }
67}
68
69/// Indicates a failure to set cell's value.
70#[derive(Debug, PartialEq, Eq)]
71pub enum ValueError {
72 /// The value is too large to fit into the cell memory.
73 ValueTooLarge { value_size: u64 },
74}
75
76impl From<ValueError> for InitError {
77 fn from(e: ValueError) -> InitError {
78 match e {
79 ValueError::ValueTooLarge { value_size } => InitError::ValueTooLarge { value_size },
80 }
81 }
82}
83
84/// Represents a value stored in stable memory.
85///
86/// A `Cell` stores a single value directly in stable memory and provides immediate persistence
87/// on every write operation. This makes it ideal for configuration values, metadata, or any
88/// small state that needs to survive canister upgrades.
89///
90/// You should use cells only for small (up to a few MiB) values to keep upgrades safe. For larger
91/// values, consider using other data structures like `Vec` or `BTreeMap` instead.
92///
93/// # Example
94///
95/// ```rust
96/// use ic_stable_structures::{Cell, DefaultMemoryImpl, Storable, storable::Bound};
97/// use std::borrow::Cow;
98/// use std::cell::RefCell;
99///
100/// #[derive(Clone)]
101/// struct Config {
102/// name: String,
103/// version: u32,
104/// }
105///
106/// // Implement Storable for serialization/deserialization when saving to stable memory.
107/// impl Storable for Config {
108/// fn to_bytes(&self) -> Cow<'_, [u8]> {
109/// # let mut bytes = Vec::new();
110/// // Convert config into bytes...
111/// # Cow::Owned(bytes)
112/// }
113///
114/// fn into_bytes(self) -> Vec<u8> {
115/// # let mut bytes = Vec::new();
116/// // Convert config into bytes...
117/// # bytes
118/// }
119///
120/// fn from_bytes(bytes: Cow<[u8]>) -> Self {
121/// // Convert bytes back to Config
122/// # let (name, version) = ("".to_string(), 0);
123/// # Config { name, version }
124/// }
125///
126/// // Types can be bounded or unbounded:
127/// // - Use Bound::Unbounded if the size can vary or isn't known in advance (recommended for most cases)
128/// // - Use Bound::Bounded if you know the maximum size and want memory optimization
129/// const BOUND: Bound = Bound::Unbounded;
130/// }
131///
132/// // Create a global cell variable
133/// thread_local! {
134/// static CONFIG: RefCell<Cell<Config, DefaultMemoryImpl>> = RefCell::new(
135/// Cell::init(
136/// DefaultMemoryImpl::default(),
137/// Config {
138/// name: "MyConfig".to_string(),
139/// version: 1,
140/// }
141/// )
142/// );
143/// }
144///
145/// // Read the current configuration
146/// fn get_version() -> u32 {
147/// CONFIG.with(|c| c.borrow().get().version)
148/// }
149///
150/// // Update the configuration
151/// fn update_version(new_version: u32) {
152/// CONFIG.with(|c| {
153/// let mut cell = c.borrow_mut();
154/// let mut config = cell.get().clone();
155/// config.version = new_version;
156/// cell.set(config);
157/// });
158/// }
159///
160/// # // Test to ensure example works as expected.
161/// # fn main() {
162/// # assert_eq!(get_version(), 1);
163/// # update_version(2);
164/// # assert_eq!(get_version(), 2);
165/// # }
166/// ```
167pub struct Cell<T: Storable, M: Memory> {
168 memory: M,
169 value: T,
170}
171
172impl<T: Storable, M: Memory> Cell<T, M> {
173 /// Creates a new cell in the specified memory, overwriting the previous contents of the memory.
174 pub fn new(memory: M, value: T) -> Self {
175 Self::flush_value(&memory, &value).expect("Failed to write initial value to the memory");
176 Self { memory, value }
177 }
178
179 /// Initializes the value of the cell based on the contents of the `memory`.
180 /// If the memory already contains a cell, initializes the cell with the decoded value.
181 /// Otherwise, sets the cell value to `default_value` and writes it to the memory.
182 pub fn init(memory: M, default_value: T) -> Self {
183 if memory.size() == 0 {
184 return Self::new(memory, default_value);
185 }
186
187 let header = Self::read_header(&memory);
188
189 if &header.magic != MAGIC {
190 return Self::new(memory, default_value);
191 }
192
193 if header.version != LAYOUT_VERSION {
194 panic!(
195 "Failed to initialize cell: {}",
196 InitError::IncompatibleVersion {
197 last_supported_version: LAYOUT_VERSION,
198 decoded_version: header.version,
199 }
200 );
201 }
202
203 Self {
204 value: Self::read_value(&memory, header.value_length),
205 memory,
206 }
207 }
208
209 /// Reads and decodes the value of specified length.
210 ///
211 /// PRECONDITION: memory is large enough to contain the value.
212 fn read_value(memory: &M, len: u32) -> T {
213 let mut buf = vec![];
214 read_to_vec(memory, HEADER_V1_SIZE.into(), &mut buf, len as usize);
215 T::from_bytes(Cow::Owned(buf))
216 }
217
218 /// Reads the header from the specified memory.
219 ///
220 /// PRECONDITION: memory.size() > 0
221 fn read_header(memory: &M) -> HeaderV1 {
222 let mut magic: [u8; 3] = [0; 3];
223 let mut version: [u8; 1] = [0; 1];
224 let mut len: [u8; 4] = [0; 4];
225
226 memory.read(0, &mut magic);
227 memory.read(3, &mut version);
228 memory.read(4, &mut len);
229
230 HeaderV1 {
231 magic,
232 version: version[0],
233 value_length: u32::from_le_bytes(len),
234 }
235 }
236
237 /// Returns the current value in the cell.
238 pub fn get(&self) -> &T {
239 &self.value
240 }
241
242 /// Returns the underlying memory.
243 pub fn into_memory(self) -> M {
244 self.memory
245 }
246
247 /// Updates the current value in the cell.
248 /// If the new value is too large to fit into the memory, the value in the cell does not
249 /// change.
250 pub fn set(&mut self, value: T) -> T {
251 Self::flush_value(&self.memory, &value).expect("Failed to write value to the memory");
252 std::mem::replace(&mut self.value, value)
253 }
254
255 /// Writes the value to the memory, growing the memory size if needed.
256 fn flush_value(memory: &M, value: &T) -> Result<(), ValueError> {
257 let encoded = value.to_bytes();
258 let bytes: &[u8] = encoded.borrow();
259 let len = bytes.len();
260 if len > u32::MAX as usize {
261 return Err(ValueError::ValueTooLarge {
262 value_size: len as u64,
263 });
264 }
265 let size = memory.size();
266 let available_space = size * WASM_PAGE_SIZE;
267 if len as u64 > available_space.saturating_sub(HEADER_V1_SIZE) || size == 0 {
268 let grow_by =
269 (len as u64 + HEADER_V1_SIZE + WASM_PAGE_SIZE - size * WASM_PAGE_SIZE - 1)
270 / WASM_PAGE_SIZE;
271 if memory.grow(grow_by) < 0 {
272 return Err(ValueError::ValueTooLarge {
273 value_size: len as u64,
274 });
275 }
276 }
277
278 debug_assert!(memory.size() * WASM_PAGE_SIZE >= len as u64 + HEADER_V1_SIZE);
279
280 let version = [LAYOUT_VERSION; 1];
281 memory.write(0, MAGIC);
282 memory.write(3, &version);
283 memory.write(4, &(len as u32).to_le_bytes());
284 memory.write(HEADER_V1_SIZE, bytes);
285 Ok(())
286 }
287}