Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ This library aims to simplify managing data structures directly in stable memory

## Available Data Structures

- [Cell]: A serializable value
- [BTreeMap]: A Key-Value store
- [BTreeSet]: A set of unique elements
- [Vec]: A growable array
- [Log]: An append-only list of variable-size entries
- [Cell]: A serializable value
- [MinHeap]: A priority queue.

## Tutorials
Expand Down
11 changes: 6 additions & 5 deletions docs/src/introduction/available-data-structures.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

The library provides several stable data structures:

* **BTreeMap**: A key-value store that maintains keys in sorted order
* **Vec**: A growable array
* **Log**: An append-only list of variable-size entries
* **Cell**: A serializable value
* **MinHeap**: A priority queue
* [Cell](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/cell/struct.Cell.html): For small single values that change rarely
* [BTreeMap](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/btreemap/struct.BTreeMap.html): A key-value store that maintains keys in sorted order
* [BTreeSet](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/btreeset/struct.BTreeSet.html): A set of unique elements
* [Vec](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/vec/struct.Vec.html): A growable array
* [Log](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/log/struct.Log.html): An append-only list of variable-size entries
* [MinHeap](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/min_heap/struct.MinHeap.html): A priority queue
89 changes: 82 additions & 7 deletions src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,89 @@ impl From<ValueError> for InitError {
}
}

/// Represents a serializable value stored in the stable memory.
/// It has semantics similar to "stable variables" in Motoko and share the same limitations.
/// The main difference is that Cell writes its value to the memory on each assignment, not just in
/// upgrade hooks.
/// You should use cells only for small (up to a few MiB) values to keep upgrades safe.
/// Represents a value stored in stable memory.
///
/// Cell is a good choice for small read-only configuration values set once on canister installation
/// and rarely updated.
/// A `Cell` stores a single value directly in stable memory and provides immediate persistence
/// on every write operation. This makes it ideal for configuration values, metadata, or any
/// small state that needs to survive canister upgrades.
///
/// You should use cells only for small (up to a few MiB) values to keep upgrades safe. For larger
/// values, consider using other data structures like `Vec` or `BTreeMap` instead.
///
/// # Example
///
/// ```rust
/// use ic_stable_structures::{Cell, DefaultMemoryImpl, Storable, storable::Bound};
/// use std::borrow::Cow;
/// use std::cell::RefCell;
///
/// #[derive(Clone)]
/// struct Config {
/// name: String,
/// version: u32,
/// }
///
/// // Implement Storable for serialization/deserialization when saving to stable memory.
/// impl Storable for Config {
/// fn to_bytes(&self) -> Cow<'_, [u8]> {
/// # let mut bytes = Vec::new();
/// // Convert config into bytes...
/// # Cow::Owned(bytes)
/// }
///
/// fn into_bytes(self) -> Vec<u8> {
/// # let mut bytes = Vec::new();
/// // Convert config into bytes...
/// # bytes
/// }
///
/// fn from_bytes(bytes: Cow<[u8]>) -> Self {
/// // Convert bytes back to Config
/// # let (name, version) = ("".to_string(), 0);
/// # Config { name, version }
/// }
///
/// // Types can be bounded or unbounded:
/// // - Use Bound::Unbounded if the size can vary or isn't known in advance (recommended for most cases)
/// // - Use Bound::Bounded if you know the maximum size and want memory optimization
/// const BOUND: Bound = Bound::Unbounded;
/// }
///
/// // Create a global cell variable
/// thread_local! {
/// static CONFIG: RefCell<Cell<Config, DefaultMemoryImpl>> = RefCell::new(
/// Cell::init(
/// DefaultMemoryImpl::default(),
/// Config {
/// name: "MyConfig".to_string(),
/// version: 1,
/// }
/// )
/// );
/// }
///
/// // Read the current configuration
/// fn get_version() -> u32 {
/// CONFIG.with(|c| c.borrow().get().version)
/// }
///
/// // Update the configuration
/// fn update_version(new_version: u32) {
/// CONFIG.with(|c| {
/// let mut cell = c.borrow_mut();
/// let mut config = cell.get().clone();
/// config.version = new_version;
/// cell.set(config);
/// });
/// }
///
/// # // Test to ensure example works as expected.
/// # fn main() {
/// # assert_eq!(get_version(), 1);
/// # update_version(2);
/// # assert_eq!(get_version(), 2);
/// # }
/// ```
pub struct Cell<T: Storable, M: Memory> {
memory: M,
value: T,
Expand Down
17 changes: 17 additions & 0 deletions src/cell/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,20 @@ fn test_cell_grow_and_shrink() {
let cell = reload(cell);
assert_eq!(&[3u8; 5][..], &cell.get()[..]);
}

#[test]
fn can_store_unbounded_type() {
let mem = VectorMemory::default();
let test_string = "Another string with different content".to_string();

let mut cell = Cell::init(mem, test_string.clone());
assert_eq!(test_string, *cell.get());

let new_string = "This is a test string that can be very long and unbounded".to_string();
cell.set(new_string.clone());
assert_eq!(new_string, *cell.get());

// Test reloading from memory
let cell = reload(cell);
assert_eq!(new_string, *cell.get());
}