Stable Variables

To persist the state of an actor when upgrading, we can declare immutable and mutable variables to be stable. Stable variables must be of stable type.

Modifying and Upgrading Stable Variables

The state of an actor is stored in the form of immutable and mutable variables that are declared with the let or var keywords. Variable declarations in actors always have private visibility (although the private keyword is not necessary and is assumed by default).

If we want to retain the state of our actor when upgrading, we need to declare all its state variables as stable. A variable can only be declared stable if it is of stable type. Many, but not all types, are stable. Mutable and immutable stable variables are declared like this:

stable let x = 0;

stable var y = 0;

These variables now retain their state even after upgrading the actor code. Lets demonstrate this with an actor that implements a simple counter.

actor {
    stable var count : Nat = 0;

    public shared query func read() : async Nat { count };

    public shared func increment() : async () { count += 1 };
};

Our actor has a mutable variable count that is declared stable. It's initial value is 0. We also have a query function read and an update function increment. The value of count is shown below in a timeline of state in two instances of our counter actor: one with var count and the other with stable var count:

TimeEventValue of
var count
Value of
stable var count
1First install of actor code
0
0
2Call increment()
1
1
3Call read()
1
1
4Call increment()
2
2
5Upgrade actor code
0
2
6Call read()
0
2
7Call increment()
1
3
8Call increment()
2
4
9Reinstall actor code
0
0
10Call read()
0
0
11Call increment()
1
1
12Call increment()
2
2

Time 1: Our initial value for count is 0 in both cases.
Time 2: An update function mutates the state in both cases.
Time 3: A query function does not mutate state in both cases.
Time 5: stable var count value is persisted after upgrade.
Time 6: var count is reset after upgrade.
Time 7: var count starts at 0, while stable var count starts at 2.
Time 10: var count and stable var count are both reset due to reinstall.

Stable types

A type is stable if it is shared and remains shared after ignoring any var keywords within it. An object with private functions is also stable. Stable types thus include all shared types plus some extra types that contain mutable variables and object types with private functions.

Stable variables can only be declared inside actors. Stable variables always have private visibility.

The following types for immutable or mutable variables in actors (in addition to all shared types) could be declared stable.

Stable Mutable Array

Immutable or mutable variables of mutable array type could be declared stable:

stable let a1 : [var Nat] = [var 0, 1, 2];

stable var a2 : [var Text] = [var "t1", "t2", "t3"];

Immutable variable a1 can be stable because [var Nat] is a mutable array type. Mutable variable a2 can be stable because [var Text] is of mutable array type.

Stable Records with Mutable Fields

Immutable or mutable variables of records with mutable fields could be declared stable:

stable let r1 = { var x = 0 };

stable var r2 = { var x = 0; y = 1 };

Immutable variable r1 and mutable variable r2 can be stable because they contain a stable type, namely a record with a mutable field.

Stable Objects with Mutable Variables

Immutable or mutable variables of objects with (private or public) mutable variables could be declared stable:

stable let o1 = object {
    public var x = 0;
    private var y = 0;
};

stable var o2 = object {
    public var x = 0;
    private var y = 0;
};

Immutable variable o1 and mutable variable o2 can be stable because they contain a stable type, namely an object with private and public mutable variables.

Stable Objects with Private Functions

Immutable or mutable variables of objects with private functions could be declared stable:

stable let p1 = object { private func f() {} };

stable var p2 = object { private func f() {} };

Immutable variable p1 and mutable variable p2 can be stable because they contain a stable type, namely an object with a private function.

Other stable types

On top all shared types and the types mentioned above, the following types could also be stable:

  • Tuples of stable types
  • Option types of any stable types
  • Variant types with associated types that are stable

How it works

Declaring variable(s) stable causes the following to happen automatically when upgrading our canister with new actor code:

  • The value of the variable(s) is serialized into an internal data format.
  • The serialized data format is copied to stable memory.
  • The upgraded actor code (in the form of a Wasm module) is installed in the canister and the state of the variables is lost.
  • The values in data format inside stable memory are retrieved and deserialized.
  • The variables of the new actor code are assigned the original deserialized values.

Example of non-stable type

A non-stable type could be an object with public functions. A variable that contains such an object, could not be declared stable!

stable let q1 = object {
    public func f() {};
};

ERROR: Variable q1 is declared stable but has non-stable type!