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
:
Time | Event | Value of var count | Value of stable var count |
---|---|---|---|
1 | First install of actor code | ||
2 | Call increment() | ||
3 | Call read() | ||
4 | Call increment() | ||
5 | Upgrade actor code | ||
6 | Call read() | ||
7 | Call increment() | ||
8 | Call increment() | ||
9 | Reinstall actor code | ||
10 | Call read() | ||
11 | Call increment() | ||
12 | Call increment() |
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!