diff --git a/README.md b/README.md index 607da382..fb268cee 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/src/introduction/available-data-structures.md b/docs/src/introduction/available-data-structures.md index 64d95b58..ccff120c 100644 --- a/docs/src/introduction/available-data-structures.md +++ b/docs/src/introduction/available-data-structures.md @@ -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 diff --git a/src/cell.rs b/src/cell.rs index 8583efa7..19425b25 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -81,14 +81,89 @@ impl From 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 { +/// # 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> = 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 { memory: M, value: T, diff --git a/src/cell/tests.rs b/src/cell/tests.rs index acfe8257..b4d1e5bd 100644 --- a/src/cell/tests.rs +++ b/src/cell/tests.rs @@ -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()); +}