canic-memory
canic-memory provides stable-memory helpers for Internet Computer canisters. It
can be used on its own, without the rest of Canic, when a crate needs one shared
memory manager, deterministic thread-local initialization, and validation for
stable-memory ID ownership.
The crate currently declares MSRV 1.91.0. The Canic workspace may build with a
newer pinned toolchain, but downstream crates compiling canic-memory from
source should only need Rust 1.91.0 or newer.
What It Provides
- A shared
MemoryManager<DefaultMemoryImpl>used by all helpers. - Per-crate memory ID range reservation and overlap validation.
ic_memory!andic_memory_range!for declarative stable-memory slots.MemoryApifor declaring startup-selected stable-memory IDs and opening validated slots.eager_static!andeager_init!for deterministic startup initialization.impl_storable_bounded!andimpl_storable_unbounded!for CBOR-backedStorableimplementations.- A
canic_cdkre-export atcanic_memory::cdk.
Install
Inside the Canic workspace, use the workspace dependency:
= { = true }
From another crate, depend on the published crate:
= "0.38"
Quick Start
Declare stable structures with eager_static! so they are touched during
startup, not lazily during the first endpoint call.
use ;
use ;
use RefCell;
;
eager_static!
Bootstrap memory during canister startup before any endpoint uses the stable structures:
use MemoryApi;
bootstrap_owner_range(...) performs the standalone startup sequence:
- Collect constructor-registered
ic_memory_key!andic_memory!declarations without opening their virtual memories. - Run every registered
eager_init!body soic_memory_range!declarations are collected. - Reserve the caller's owner range and validate the sealed declaration snapshot against the persisted ledger.
- Touch every
eager_static!thread-local after validation so stable stores can open their already-approved memory handles.
When using the full Canic facade (canic::start! or canic::start_root!), Canic
runs this lifecycle wiring for you.
Memory Ranges
Stable-memory IDs are global inside one canister. Reserve a range for each crate that owns stable structures, then keep that crate's IDs inside the range.
use ;
eager_init!;
Range validation catches:
- overlapping ranges
start > end- duplicate IDs
- IDs outside the owner's reserved range
- IDs owned by another crate
- historical reuse of an ID or range recorded by the persisted layout ledger
- ID
255, which is the unallocated-bucket sentinel and is not a usable virtual memory ID
Exact duplicate range reservations for the same crate are allowed so init and post-upgrade can share the same bootstrap path.
canic-memory reserves ID 0 for the persisted layout ledger. ID 0 stores
every owner range and memory ID that has been registered through the bootstrap
path, so removed declarations remain historical reservations rather than
becoming silently reusable. Canic framework keys (canic.*) must use IDs
0-99; downstream application keys must use 100-254. IDs 1-99 are Canic
framework expansion budget, not application space. The full Canic runtime stack
currently uses 5-10 for control-plane stores and 11-99 for core runtime
stores and future framework allocation.
Bootstrap distinguishes brand-new stable memory from existing state before
mutating the declaration snapshot. Empty stable memory may initialize the
genesis ledger. Foreign or corrupt raw stable memory fails closed, and existing
MemoryManager state must already contain a valid ID 0 Canic ABI ledger.
Runtime-Selected Slots
Use MemoryApi when the memory ID is chosen during startup and
ic_memory_key! is not a good fit. Declaration and opening are separate:
declare the slot before bootstrap, then open it only after bootstrap validates
the sealed declaration snapshot. Endpoint code must not call declaration APIs
as a dynamic allocator.
use MemoryApi;
MemoryApi::declare_with_key(...) is the allocation claim. It is accepted only
before bootstrap seals the runtime declaration snapshot and it does not open the
underlying virtual memory. MemoryApi::register_with_key(...) opens an
already-validated slot; it is not a dynamic allocation API. Reusing the same ID
for a different stable key or moving the same stable key to another ID remains
fatal. Owner and label metadata may change across refactors; the stable key
must not.
If a store wants diagnostic schema metadata in the ledger, use
declare_with_key_metadata(...):
declare_with_key_metadata
.expect;
Schema metadata is optional and informational in 0.38. It does not change
allocation ownership and is not fatal during bootstrap when it drifts for the
same stable key and ID. The ledger records the latest values and preserves
declaration history for diagnostics. When present, schema_version must be
greater than zero. schema_fingerprint must be a non-empty ASCII string, must
not contain ASCII control characters, and must fit within 256 bytes.
Registry Introspection
Use the supported MemoryApi reads for validation, diagnostics, or endpoint
responses:
use MemoryApi;
Lower-level registry snapshot helpers also exist for debugging and tests:
MemoryApi::ledger_snapshot()for a fallible persisted-ledger diagnostic read, including the canonical0-99Canic and100-254application authority ranges. On wasm this path decodes only the ID0ABI ledger from raw stable memory so operators can inspect ledger state without opening application or framework stores.MemoryRegistry::export_range_entries()MemoryRegistry::export_ids_by_range()
Prefer MemoryApi for normal supported reads.
Storable Helpers
The storable macros implement ic-stable-structures Storable with Canic's
shared CBOR serializer.
use impl_storable_bounded;
use ;
impl_storable_bounded!;
Use impl_storable_bounded!(Type, max_size, is_fixed_size) when the serialized
size has a known bound. Use impl_storable_unbounded!(Type) only for data that
is expected to grow beyond a practical fixed bound.
Standalone Lifecycle
For standalone canisters, call one of the bootstrap helpers from init and post-upgrade before handling user calls:
use MemoryApi;
If all owner ranges are already queued through ic_memory_range!, and the
caller does not need to reserve an additional initial range, use:
use MemoryApi;
Accessing an ic_memory! or ic_memory_key! slot before bootstrap will panic
with a message pointing back to memory bootstrap. This is intentional: stable
memory layout problems should fail during lifecycle startup, not during a user
call.
Testing
Unit tests that touch the registry can reset global state with
registry::reset_for_tests():
reset_for_tests() is only available under cfg(test).
Module Map
api- supported runtime API for bootstrapping, registration, and reads.manager- shared thread-local memory manager.registry- range reservation, ID registration, pending queues, and errors.runtime- eager TLS execution and registry startup glue.macros- exported memory, runtime, and storable macros.serialize- CBOR serialization helpers used by storable macros.
Notes
- Memory IDs are
u8values. Canic uses0-99; application code uses100-254; ID255is the unallocated-bucket sentinel and is permanently invalid as a virtual memory ID. - Prefer
ic_memory_key!for every Canic-managed memory. The stable key is the ABI identity and should not be renamed when packages, modules, or marker types move.ic_memory!remains available for standalone explicit-ID users outside the Canic runtime bootstrap contract. - Consumers outside Canic can import only
canic-memorypluscanic-cdk; the rest of the Canic stack is optional.