ic-memory
ic-memory helps Internet Computer canisters avoid opening the wrong stable
memory after an upgrade.
It remembers this mapping forever:
logical store -> physical stable-memory slot
If a future version tries to move that store to a different slot, or reuse that
slot for a different store, ic-memory rejects the layout before stable-memory
handles are opened.
Why Use It?
Use ic-memory when a canister has more than one stable store and the layout
can change over time.
It is most useful for frameworks, generated canisters, multi-store apps, plugin systems, and canister families that evolve across releases.
You probably do not need it for a tiny canister with one hand-written stable structure and a fixed layout.
The Bug
Version 1 ships with:
app.users.v1 -> MemoryManager ID 100
app.orders.v1 -> MemoryManager ID 101
A later upgrade accidentally ships with:
app.users.v1 -> MemoryManager ID 101
app.orders.v1 -> MemoryManager ID 100
That can still compile. It can even install.
But now the canister may open orders data as users data, and users data as
orders data. ic-memory catches that mismatch first.
Quick Start
Declare the MemoryManager IDs your crate owns:
ic_memory_range!;
Open stable structures through ic_memory_key!:
use RefCell;
thread_local!
Bootstrap once before touching stable data:
That is the normal path.
Multi-Crate Composition
Every crate registers into the same linked ic-memory runtime. Crates do not
need to import or name each other:
Bootstrap validates the complete layout from every linked crate, commits the
allocation ledger, and publishes validated allocations. TLS-backed stores open
when your code first touches the thread_local!.
Duplicate stable keys, duplicate MemoryManager IDs, overlapping ranges, and out-of-range declarations fail before stable structures open.
ic-memory follows the ic-stable-structures::MemoryManager ID domain exactly:
IDs 0..=254 are usable, and ID 255 is always the unallocated sentinel. It is
not an application slot and cannot be declared or reserved.
Range claims are authoritative in the default runtime. If a crate registers
ic_memory_range!, its declared memories must stay inside that range. Framework
adapters that want their own range policy, such as Canic, should register only
the ranges they want ic-memory to enforce and put the rest in their policy
adapter.
The validated allocation state is an in-memory capability produced by bootstrap; it is not a serde payload and should not be treated as configuration.
Stable Keys
Stable keys are permanent logical store names. They should describe ownership and purpose, not the current memory ID.
namespace.component.store_or_role.vN
Examples:
use StableKey;
parse.expect;
parse.expect;
parse.expect;
Changing a key creates a new logical allocation identity. If the durable store is the same, keep the stable key and update schema metadata instead.
More Detail
The short version:
declare ranges
register stable stores
bootstrap once
only then open stable memory
Framework authors and policy adapters should read ADVANCED.md. The non-negotiable invariants are recorded in SAFETY.md.
ic-memory is early infrastructure extracted from Canic. It owns allocation
governance, not schema migration, endpoint routing, authorization, or data
semantics.