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}