Skip to main content

Crate ic_memory

Crate ic_memory 

Source
Expand description

§ic-memory

Animated warning banner

EARLY INFRASTRUCTURE: validate before opening stable 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.

Meme showing ic-memory keeping ic-stable-structures stable memory allocations from drifting

§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::ic_memory_range!(start = 120, end = 129);

Open stable structures through ic_memory_key!:

use std::cell::RefCell;

thread_local! {
    pub static USERS: RefCell<UsersStore> = RefCell::new(UsersStore::init(
        ic_memory::ic_memory_key!(
            "icydb.test_db.users.data.v1",
            UsersStore,
            120,
        )
    ));
}

Bootstrap once before touching stable data:

#[ic_cdk::init]
fn init() {
    ic_memory::bootstrap_default_memory_manager().expect("valid stable-memory layout");
}

#[ic_cdk::post_upgrade]
fn post_upgrade() {
    ic_memory::bootstrap_default_memory_manager().expect("valid stable-memory layout");
}

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:

mod package_a {
    ic_memory::ic_memory_range!(start = 100, end = 109);

    thread_local! {
        pub static USERS: RefCell<UsersStore> = RefCell::new(UsersStore::init(
            ic_memory::ic_memory_key!("package_a.users.v1", UsersStore, 100)
        ));
    }
}

mod package_b {
    ic_memory::ic_memory_range!(start = 110, end = 119);

    thread_local! {
        pub static ORDERS: RefCell<OrdersStore> = RefCell::new(OrdersStore::init(
            ic_memory::ic_memory_key!("package_b.orders.v1", OrdersStore, 110)
        ));
    }
}

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 ic_memory::StableKey;

StableKey::parse("app.orders.v1").expect("app key");
StableKey::parse("myapp.audit_log.v1").expect("app key");
StableKey::parse("icydb.test_db.users.data.v1").expect("database key");

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.

Retro computer warning: don't overwrite your memory

Stable-memory allocation-governance primitives for Internet Computer canister upgrades.

ic-memory prevents stable-memory slot drift.

Once a stable key is committed to a physical allocation slot, future binaries must either reopen that same stable key on that same slot or declare a new stable key.

The crate records and validates durable ownership in both directions: an active stable key cannot move to a different physical slot, and an active physical slot cannot be reused by a different stable key.

The intended integration flow is:

  1. Recover the persisted allocation ledger.
  2. Declare the stable stores expected by the current binary.
  3. Validate those declarations against ledger history and any framework policy.
  4. Commit the next generation.
  5. Only then open stable-memory handles through a validated allocation session.

This crate owns allocation invariants, not framework policy. Namespace rules, controller authorization, endpoint lifecycle, schema migrations, and application validation belong to the framework or application.

For the default MemoryManager runtime, registered ic-memory range claims are generic allocation policy and are enforced before caller-supplied policy. A framework such as Canic that wants higher-level range semantics should adapt to this contract deliberately: either register the ranges it wants ic-memory to enforce, or omit user ranges and enforce application space through its own AllocationPolicy.

Use these primitives before opening stable-memory handles. Integrations should recover the historical ledger, declare the stores expected by the current binary, validate declarations against history and policy, commit a new generation, and only then publish a validated allocation session that can open slots through a storage substrate.

AllocationBootstrap is the golden path for whichever layer owns a given ledger store. Canic may own bootstrap for a framework canister and compose IcyDB/application declarations through its registry; IcyDB may own bootstrap directly for generated database stores; or a standalone application canister may own bootstrap itself. Exactly one owner should bootstrap one ledger store. Multiple layers in the same canister must either compose declarations into that owner or use distinct ledger stores and allocation domains.

ic-stable-structures MemoryManager IDs are the first-class supported physical slot substrate. That ID domain is u8: IDs 0..=254 are usable, and ID 255 is always the ic-stable-structures unallocated sentinel. The crate still keeps narrow internal abstractions for storage adapters and diagnostics, but the native IC path is MemoryManager ID 0 -> ic-stable-structures::Cell<StableCellLedgerRecord, _> -> LedgerCommitStore -> CommittedGenerationBytes -> LedgerPayloadEnvelope -> RecoveredLedger -> ValidatedAllocations.

ic-memory is not a replacement for ic-stable-structures collections and does not wrap typed stores such as StableBTreeMap.

Re-exports§

pub use bootstrap::AllocationBootstrap;
pub use bootstrap::BootstrapCommit;
pub use bootstrap::BootstrapError;
pub use bootstrap::BootstrapReservationError;
pub use bootstrap::BootstrapRetirementError;
pub use declaration::AllocationDeclaration;
pub use declaration::DeclarationCollector;
pub use declaration::DeclarationSnapshot;
pub use declaration::DeclarationSnapshotError;
pub use diagnostics::DiagnosticExport;
pub use diagnostics::DiagnosticGeneration;
pub use diagnostics::DiagnosticRecord;
pub use key::StableKey;
pub use key::StableKeyError;
pub use ledger::AllocationHistory;
pub use ledger::AllocationLedger;
pub use ledger::AllocationRecord;
pub use ledger::AllocationReservationError;
pub use ledger::AllocationRetirement;
pub use ledger::AllocationRetirementError;
pub use ledger::AllocationStageError;
pub use ledger::AllocationState;
pub use ledger::CURRENT_LEDGER_SCHEMA_VERSION;
pub use ledger::CURRENT_PHYSICAL_FORMAT_ID;
pub use ledger::GenerationRecord;
pub use ledger::LedgerCommitError;
pub use ledger::LedgerCommitStore;
pub use ledger::LedgerCompatibilityError;
pub use ledger::LedgerIntegrityError;
pub use ledger::LedgerPayloadEnvelope;
pub use ledger::LedgerPayloadEnvelopeError;
pub use ledger::RecoveredLedger;
pub use ledger::SchemaMetadataRecord;
pub use physical::AuthoritativeSlot;
pub use physical::CommitRecoveryError;
pub use physical::CommitSlotDiagnostic;
pub use physical::CommitSlotIndex;
pub use physical::CommitStoreDiagnostic;
pub use physical::CommittedGenerationBytes;
pub use physical::DualCommitStore;
pub use physical::DualProtectedCommitStore;
pub use physical::ProtectedGenerationSlot;
pub use physical::select_authoritative_slot;
pub use policy::AllocationPolicy;
pub use registry::StaticMemoryDeclaration;
pub use registry::StaticMemoryDeclarationError;
pub use registry::StaticMemoryRangeDeclaration;
pub use registry::collect_static_memory_declarations;
pub use registry::register_static_memory_declaration;
pub use registry::register_static_memory_manager_declaration;
pub use registry::register_static_memory_manager_declaration_with_schema;
pub use registry::register_static_memory_manager_range;
pub use registry::register_static_memory_range_declaration;
pub use registry::static_memory_declaration_snapshot;
pub use registry::static_memory_declarations;
pub use registry::static_memory_range_authority;
pub use registry::static_memory_range_declarations;
pub use runtime::RuntimeBootstrapError;
pub use runtime::RuntimeOpenError;
pub use runtime::RuntimePolicyError;
pub use runtime::bootstrap_default_memory_manager;
pub use runtime::bootstrap_default_memory_manager_with_policy;
pub use schema::SchemaMetadata;
pub use schema::SchemaMetadataError;
pub use session::AllocationSession;
pub use session::AllocationSessionError;
pub use session::ValidatedAllocations;
pub use slot::AllocationSlot;
pub use slot::AllocationSlotDescriptor;
pub use slot::AllocationSlotDescriptorError;
pub use slot::IC_MEMORY_AUTHORITY_OWNER;
pub use slot::IC_MEMORY_AUTHORITY_PURPOSE;
pub use slot::IC_MEMORY_LEDGER_LABEL;
pub use slot::IC_MEMORY_LEDGER_STABLE_KEY;
pub use slot::IC_MEMORY_STABLE_KEY_PREFIX;
pub use slot::MEMORY_MANAGER_DESCRIPTOR_VERSION;
pub use slot::MEMORY_MANAGER_GOVERNANCE_MAX_ID;
pub use slot::MEMORY_MANAGER_INVALID_ID;
pub use slot::MEMORY_MANAGER_LEDGER_ID;
pub use slot::MEMORY_MANAGER_MAX_ID;
pub use slot::MEMORY_MANAGER_MIN_ID;
pub use slot::MEMORY_MANAGER_SUBSTRATE;
pub use slot::MemoryManagerAuthorityRecord;
pub use slot::MemoryManagerIdRange;
pub use slot::MemoryManagerRangeAuthority;
pub use slot::MemoryManagerRangeAuthorityError;
pub use slot::MemoryManagerRangeError;
pub use slot::MemoryManagerRangeMode;
pub use slot::MemoryManagerSlotError;
pub use slot::is_ic_memory_stable_key;
pub use slot::memory_manager_governance_range;
pub use slot::validate_memory_manager_id;
pub use stable_cell::STABLE_CELL_HEADER_SIZE;
pub use stable_cell::STABLE_CELL_LAYOUT_VERSION;
pub use stable_cell::STABLE_CELL_MAGIC;
pub use stable_cell::STABLE_CELL_VALUE_OFFSET;
pub use stable_cell::StableCellLedgerError;
pub use stable_cell::StableCellLedgerRecord;
pub use stable_cell::StableCellPayloadError;
pub use stable_cell::decode_stable_cell_ledger_record;
pub use stable_cell::decode_stable_cell_payload;
pub use stable_cell::validate_stable_cell_ledger_memory;
pub use substrate::LedgerAnchor;
pub use substrate::StorageSubstrate;
pub use validation::AllocationValidationError;
pub use validation::Validate;
pub use validation::validate_allocations;
pub use ic_stable_structures as stable_structures;

Modules§

bootstrap
declaration
diagnostics
key
ledger
physical
policy
registry
runtime
schema
session
slot
stable_cell
substrate
validation

Macros§

eager_init
Register one pre-bootstrap hook.
ic_memory_declaration
Register a MemoryManager allocation declaration during static initialization.
ic_memory_key
Declare and open a validated MemoryManager slot by stable key.
ic_memory_range
Declare a MemoryManager allocation range during static initialization.