ic-memory 0.25.4

Durable stable-memory allocation governance for Internet Computer canisters
Documentation

Jump to: What it does · Why it matters · Is it useful? · How it works · Choose an integration style · Quick start · Troubleshooting · Advanced integration

What it does

ic-memory is a safety system for an Internet Computer application's persistent data.

When an application is upgraded, its code changes but its stored data remains. Each database, log, queue, or settings store must reconnect to the same storage location it used before. If two stores are accidentally swapped, the new code can interpret one kind of data as another.

ic-memory remembers which storage location belongs to each named store. Before the application opens any of those stores, it checks the new layout against the saved allocation history. A conflicting upgrade stops with an error instead of opening the wrong data.

ic-memory protects the connection between a store and its storage location. It is not a backup system, a database schema migrator, or a data validator.

Why this matters

Imagine that version 1 of an application stores users and orders separately:

Users  -> storage location 100
Orders -> storage location 101

A later version accidentally reverses those locations:

Users  -> storage location 101
Orders -> storage location 100

The diagram below shows the same mistake and the point where ic-memory intervenes:

The program can still compile, and the upgrade can still install. Without an allocation check, it may then read order records as users and user records as orders. ic-memory detects the changed ownership before either location is opened.

This protection matters because a stable-memory location is a durable part of an application's storage layout, even though it can look like an ordinary number in source code.

Is it useful for my application?

ic-memory is most useful for frameworks, generated canisters, multi-store applications, plugin systems, and canister families that evolve over time.

How it works

The lifecycle has three parts:

  1. Name each store. The application gives every persistent store a permanent identity, such as app.users.v1.
  2. Remember its location. On the first successful installation, ic-memory records which storage location belongs to each name.
  3. Check before opening. On every installation or upgrade, the new application version declares the layout it expects. ic-memory compares that layout with the remembered one before any application store opens.

If the layouts agree, the application receives permission to open its stores. If a known name moved, a location changed owner, or another allocation rule is broken, the check returns an error and ic-memory grants no permission to open application stores.

This check-and-record operation is called bootstrap. Its important guarantee is validation before open: a diagnostic report or an uncommitted validation result cannot grant access to a store. The runtime publishes permission only after the allocation ledger has been recovered, checked, and durably updated.

What happens when a check fails?

Bootstrap returns an error before application stores are opened. ic-memory does not silently repair, move, discard, or reinterpret a conflicting allocation. A failed attempt publishes no permission to open stores.

Treat the error as an upgrade-safety signal: keep the existing stable memory, inspect the declared keys, IDs, ranges, policy, and diagnostics, then correct the new application version. Do not erase the allocation ledger or replace it with an empty one to make the error disappear. See the symptom-based troubleshooting guide for safe next steps.

What it protects

Retiring a store does not make its old location reusable. That tombstone is intentional: reusing the location could make a rollback open unrelated data.

Terminology

  • Stable memory is persistent storage that survives an Internet Computer canister upgrade.
  • A stable key is a permanent, human-readable name for one store, such as app.orders.v1.
  • A memory ID or slot is the numbered MemoryManager storage location behind that name.
  • An authority is an ownership label. It prevents one component from claiming storage assigned to another component.
  • A range grant is a group of memory IDs that an authority may use.
  • Bootstrap is the check-and-commit step that must succeed before the application opens its stores.
  • The allocation ledger is ic-memory's durable record of which stable key owns which slot.

Choose an integration style

Most integrations use one of these paths:

Fixed and automatic declarations may coexist in one host. The application that owns the concrete runtime still bootstraps the combined layout exactly once. Libraries adopting that runtime verify their own requirements and then open their committed keys; they do not bootstrap independently.

Quick start for developers

Add the crate:

[dependencies]
ic-memory = "0.25.4"

ic-memory re-exports its exact ic-stable-structures dependency through ic_memory::ic_stable_structures. Import collections, backing memories, and traits through that namespace so their types match the runtime:

use ic_memory::{
    RuntimeMemory,
    ic_stable_structures::{Cell, DefaultMemoryImpl},
};

type CounterStore = Cell<u64, RuntimeMemory<DefaultMemoryImpl>>;

A separate ic-stable-structures dependency is unnecessary for those imports.

Declare a fixed storage location

Give the component an authority, grant it a range, and declare the permanent key and ID of each store:

const MEMORY_AUTHORITY: &str = "example_app";

ic_memory::ic_memory_range!(
    authority = MEMORY_AUTHORITY,
    start = 120,
    end = 129,
);

ic_memory::ic_memory_declaration!(
    authority = MEMORY_AUTHORITY,
    key = "example_app.users.v1",
    label = "UsersStore",
    id = 120,
);

fn initialize_stable_storage() -> Result<(), Box<dyn std::error::Error>> {
    ic_memory::bootstrap_default_memory_manager()?;
    let users = ic_memory::open_default_memory_manager_memory(
        "example_app.users.v1",
        120,
    )?;
    drop(users);
    Ok(())
}

Call the bootstrap function from both the canister's initialization and post-upgrade lifecycle before code touches any stable collection.

fn bootstrap_memory() {
    ic_memory::bootstrap_default_memory_manager()
        .expect("stable-memory allocation layout must be valid");
}

#[ic_cdk::init]
fn init() {
    bootstrap_memory();
}

#[ic_cdk::post_upgrade]
fn post_upgrade() {
    bootstrap_memory();
}

These lifecycle functions must run before any thread-local or deferred initialization opens a stable collection. Applications using a custom policy or bucket configuration call the corresponding bootstrap helper in the same locations.

The default range mode is Reserved: it permits declared fixed IDs but does not provide new automatic allocations.

Let the host assign a location

Libraries can request a durable key without choosing an ID. The application that owns the runtime grants an explicit Allowed pool:

ic_memory::ic_memory_range!(
    authority = "example_app",
    start = 10,
    end = 254,
    mode = Allowed,
);
ic_memory::ic_memory_declaration!(
    authority = "example_app",
    key = "example_app.users.v1",
);

fn initialize() -> Result<(), Box<dyn std::error::Error>> {
    ic_memory::bootstrap_default_memory_manager()?;
    let assigned_id =
        ic_memory::default_memory_manager_memory_id("example_app.users.v1")?;
    let users =
        ic_memory::open_default_memory_manager_memory_by_key("example_app.users.v1")?;
    drop((assigned_id, users));
    Ok(())
}

Known keys keep their committed IDs. New requests are sorted by stable key and receive the lowest unclaimed ID in their authority's Allowed ranges. Fixed, reserved, omitted, and retired allocations remain unavailable. If no eligible ID remains, bootstrap returns MemoryResolutionError::Exhausted.

For examples of both allocation styles, see examples/key_only.rs and examples/composed_host.rs.

Applications composed from several libraries

Every linked crate contributes declarations to one immutable registry. The application grants each component only its intended range and bootstraps the combined layout once.

Libraries adopting an already bootstrapped host can verify that all of their requirements were included without rerunning bootstrap or replacing the host's policy:

fn adopt_host() -> Result<(), Box<dyn std::error::Error>> {
    let requirements = ic_memory::sealed_declaration_snapshot()?;
    ic_memory::verify_default_memory_manager_authority(
        &requirements,
        "library_name",
    )?;
    Ok(())
}

Duplicate stable keys, duplicate memory IDs, overlapping ranges, and out-of-range declarations fail before stable structures open. Hosts must also declare or reserve allocations used by raw MemoryManager clients; diagnostics cannot infer ownership from bytes alone.

The default runtime reserves IDs 0..=9 and keys under ic_memory.* for its own governance records. ID 0 contains the allocation ledger. Application code may use IDs through 254; ID 255 is the upstream unallocated sentinel and can never be declared.

Stable keys

A stable key names the durable identity of a store, not its current numeric location:

namespace.component.store_or_role.vN
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 allocation identity. If the durable store is still the same store, keep its key and change its optional diagnostic schema metadata instead.

Frequently asked questions

No. It validates allocation identity. Schema and data migrations remain the application's responsibility.

No. Keep a separate backup and disaster-recovery plan. ic-memory fails closed when its allocation metadata cannot be recovered safely.

Bootstrap returns an error and publishes no open capability. Fix the proposed layout or policy; do not erase the ledger to bypass the conflict.

No. Retirement is a permanent tombstone so a future version or rollback cannot mistake unrelated data for the retired store.

No. One owner bootstraps each concrete runtime. Libraries verify their requirements against the host's committed layout and open only their keys.

Use fixed IDs when the application deliberately manages its layout. Use automatic allocation when a host should place reusable components within explicitly granted ranges. Both preserve the assigned ID after commitment.

Yes, provided its key is new, its fixed ID or automatic range is eligible, and the complete layout passes current policy and historical validation.

Operations and advanced integration

ic-memory is pre-1.0 infrastructure extracted from Canic. Current releases use the current API and durable format only; earlier pre-1.0 wire formats are not a compatibility target.