The Memory Trait

Stable structures are responsible for managing their own memory. To provide maximum flexibility, the library introduces the Memory trait:

#![allow(unused)]
fn main() {
pub trait Memory {
    /// Equivalent to WebAssembly memory.size.
    fn size(&self) -> u64;

    /// Equivalent to WebAssembly memory.grow.
    fn grow(&self, pages: u64) -> i64;

    /// Copies bytes from this memory to the heap (in Wasm, memory 0).
    fn read(&self, offset: u64, dst: &mut [u8]);

    /// Writes bytes from the heap (in Wasm, memory 0) to this memory.
    fn write(&self, offset: u64, src: &[u8]);
}
}

The Memory trait intentionally models a WebAssembly memory instance. This design choice ensures consistency with the interface of memories available to canisters. It also provides future compatibility with potential multi-memory support in canisters.

Available Memory Implementations

The library provides several implementations of the Memory trait, each designed for specific use cases:

  • Ic0StableMemory: Stores data in the Internet Computer's stable memory
  • VectorMemory: An in-memory implementation backed by a Rust Vec<u8>
  • DefaultMemoryImpl: A smart implementation that automatically selects the appropriate memory backend:
    • Uses Ic0StableMemory when running in an Internet Computer canister (wasm32 target)
    • Falls back to VectorMemory in other environments (like tests or non-IC contexts)

Additional implementations such as FileMemory and RestrictedMemory exist but are less commonly used.

In most cases, you should use DefaultMemoryImpl as your memory implementation.

Usage Example

Here's how to initialize a stable BTreeMap using DefaultMemoryImpl:

#![allow(unused)]
fn main() {
use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
}

Important: Stable structures cannot share memories. Each memory must be dedicated to a single stable structure.

While the above example works correctly, it demonstrates a potential issue: the BTreeMap will use the entire stable memory. This becomes problematic when trying to use multiple stable structures. For example, the following code will fail in a canister:

#![allow(unused)]
fn main() {
use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_2: BTreeMap<u64, u64, _> = 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.
}

The code fails because both map_1 and map_2 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, which creates up to 255 virtual memories from a single memory instance. We'll explore this solution in the next section.