diff --git a/README.md b/README.md index 15649f55..b5af4899 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,9 @@ use ic_stable_structures::{ BTreeMap, DefaultMemoryImpl, }; let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default()); -let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(MemoryId::new(0))); -let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(MemoryId::new(1))); +let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); +let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); map_a.insert(1, b'A'); map_b.insert(1, b'B'); diff --git a/docs/src/concepts/memory-manager.md b/docs/src/concepts/memory-manager.md index 5b665f23..04c18c9f 100644 --- a/docs/src/concepts/memory-manager.md +++ b/docs/src/concepts/memory-manager.md @@ -22,8 +22,9 @@ use ic_stable_structures::{ let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default()); // Create two separate BTreeMaps, each with its own virtual memory -let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(MemoryId::new(0))); -let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(MemoryId::new(1))); +let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); +let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); // Demonstrate independent operation of the two maps map_a.insert(1, b'A'); diff --git a/docs/src/concepts/memory-trait.md b/docs/src/concepts/memory-trait.md index 588e30b1..1eedef6a 100644 --- a/docs/src/concepts/memory-trait.md +++ b/docs/src/concepts/memory-trait.md @@ -55,12 +55,15 @@ Here's how to initialize a stable `BTreeMap` using `DefaultMemoryImpl`: ```rust use ic_stable_structures::{BTreeMap, DefaultMemoryImpl}; -let mut map: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); +let mut map: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); + +map.insert(1, "hello".to_string()); +assert_eq!(map.get(&1), Some("hello".to_string())); ``` ```admonish warning "" -**Important**: Stable structures cannot share memories. -Each memory must be dedicated to a single stable structure. +**⚠️ CRITICAL:** Stable structures **MUST NOT** share memories! +Each memory must belong to only one stable structure. ``` While the above example works correctly, it demonstrates a potential issue: the `BTreeMap` will use the entire stable memory. @@ -69,15 +72,16 @@ For example, the following code will fail in a canister: ```rust use ic_stable_structures::{BTreeMap, DefaultMemoryImpl}; -let mut map_1: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); -let mut map_2: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); +let mut map_a: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); +let mut map_b: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); -map_1.insert(1, 2); -map_2.insert(1, 3); -assert_eq!(map_1.get(&1), Some(2)); // This assertion fails. +map_a.insert(1, b'A'); +map_b.insert(1, b'B'); +assert_eq!(map_a.get(&1), Some(b'A')); // ❌ FAILS: Returns b'B' due to shared memory! +assert_eq!(map_b.get(&1), Some(b'B')); // ✅ Succeeds, but corrupted map_a ``` -The code fails because both `map_1` and `map_2` are using the same stable memory. +The code fails because both `map_a` and `map_b` are using the same stable memory. This causes changes in one map to affect or corrupt the other. To solve this problem, the library provides the [MemoryManager](./memory-manager.md), which creates up to 255 virtual memories from a single memory instance. diff --git a/src/btreemap.rs b/src/btreemap.rs index cdb6364e..66a56035 100644 --- a/src/btreemap.rs +++ b/src/btreemap.rs @@ -112,8 +112,8 @@ const PAGE_SIZE_VALUE_MARKER: u32 = u32::MAX; /// /// ## Multiple BTreeMaps and Memory Management /// -/// **Important**: Each stable structure requires its own designated memory region. Attempting to -/// initialize multiple structures with the same memory will lead to data corruption. +/// > **⚠️ CRITICAL:** Stable structures **MUST NOT** share memories! +/// > Each memory must belong to only one stable structure. /// /// ### What NOT to do: /// @@ -121,13 +121,14 @@ const PAGE_SIZE_VALUE_MARKER: u32 = u32::MAX; /// use ic_stable_structures::{BTreeMap, DefaultMemoryImpl}; /// /// // ERROR: Using the same memory for multiple BTreeMaps will corrupt data -/// let mut map_1: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); -/// let mut map_2: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); +/// let mut map_a: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); +/// let mut map_b: BTreeMap = BTreeMap::init(DefaultMemoryImpl::default()); /// -/// map_1.insert(1, "two".to_string()); -/// map_2.insert(1, "three".to_string()); -/// // This assertion would fail: changes to map_2 corrupt map_1's data -/// assert_eq!(map_1.get(&1), Some("two".to_string())); +/// map_a.insert(1, b'A'); +/// map_b.insert(1, b'B'); +/// // This assertion would fail: changes to map_b corrupt map_a's data +/// assert_eq!(map_a.get(&1), Some(b'A')); // ❌ FAILS: Returns b'B' due to shared memory! +/// assert_eq!(map_b.get(&1), Some(b'B')); // ✅ Succeeds, but corrupted map_a /// ``` /// /// ### Correct approach using MemoryManager: @@ -137,18 +138,15 @@ const PAGE_SIZE_VALUE_MARKER: u32 = u32::MAX; /// memory_manager::{MemoryId, MemoryManager}, /// BTreeMap, DefaultMemoryImpl, /// }; +/// let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default()); +/// let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1)); +/// let mut map_a: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_a)); +/// let mut map_b: BTreeMap = BTreeMap::init(mem_mgr.get(mem_id_b)); /// -/// // Initialize the memory manager with a single memory -/// let memory_manager = MemoryManager::init(DefaultMemoryImpl::default()); -/// -/// // Get separate virtual memories for each BTreeMap -/// let mut map_1: BTreeMap = BTreeMap::init(memory_manager.get(MemoryId::new(0))); -/// let mut map_2: BTreeMap = BTreeMap::init(memory_manager.get(MemoryId::new(1))); -/// -/// map_1.insert(1, "two".to_string()); -/// map_2.insert(1, "three".to_string()); -/// // Now this works as expected -/// assert_eq!(map_1.get(&1), Some("two".to_string())); +/// map_a.insert(1, b'A'); +/// map_b.insert(1, b'B'); +/// assert_eq!(map_a.get(&1), Some(b'A')); // ✅ Succeeds: Each map has its own memory +/// assert_eq!(map_b.get(&1), Some(b'B')); // ✅ Succeeds: No data corruption /// ``` /// /// The [`MemoryManager`](crate::memory_manager::MemoryManager) creates up to 255 virtual memories diff --git a/src/memory_manager.rs b/src/memory_manager.rs index 5d76705e..ab9893ff 100644 --- a/src/memory_manager.rs +++ b/src/memory_manager.rs @@ -22,22 +22,22 @@ //! let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default()); //! //! // Create different memories, each with a unique ID. -//! let memory_0 = mem_mgr.get(MemoryId::new(0)); -//! let memory_1 = mem_mgr.get(MemoryId::new(1)); +//! let memory_a = mem_mgr.get(MemoryId::new(0)); +//! let memory_b = mem_mgr.get(MemoryId::new(1)); //! //! // Each memory can be used independently. -//! memory_0.grow(1); -//! memory_0.write(0, &[1, 2, 3]); +//! memory_a.grow(1); +//! memory_a.write(0, &[1, 2, 3]); //! -//! memory_1.grow(1); -//! memory_1.write(0, &[4, 5, 6]); +//! memory_b.grow(1); +//! memory_b.write(0, &[4, 5, 6]); //! //! let mut bytes = vec![0; 3]; -//! memory_0.read(0, &mut bytes); +//! memory_a.read(0, &mut bytes); //! assert_eq!(bytes, vec![1, 2, 3]); //! //! let mut bytes = vec![0; 3]; -//! memory_1.read(0, &mut bytes); +//! memory_b.read(0, &mut bytes); //! assert_eq!(bytes, vec![4, 5, 6]); //! ``` use crate::{ @@ -170,9 +170,9 @@ impl MemoryManager { /// /// **Usage Pattern:** /// ```rust,ignore - /// drop(map); // 1. Drop the structure object first - /// let pages = memory_manager.reclaim_memory(memory_id); // 2. Reclaim memory - /// let new_map = BTreeMap::new(memory_manager.get(memory_id)); // 3. Create new structure + /// drop(map); // 1. Drop the structure object first + /// let pages = mem_mgr.reclaim_memory(memory_id); // 2. Reclaim memory + /// let new_map = BTreeMap::new(mem_mgr.get(memory_id)); // 3. Create new structure /// ``` /// /// **DANGER**: Using the original structure after reclamation causes data corruption.