7po3j-syaaa-aaaal-qbqea-cai.icp0.io Open in urlscan Pro
2a0b:21c0:b002:2:5000:edff:fe0d:98de  Public Scan

URL: https://7po3j-syaaa-aaaal-qbqea-cai.icp0.io/advanced-concepts/scalability/actor-classes.html
Submission: On December 28 via api from US — Scanned from GB

Form analysis 1 forms found in the DOM

<form id="searchbar-outer" class="searchbar-outer">
  <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>

Text Content

 1.  The Motoko Programming Language
 2.  
 3.  Part 1
 4.  1. Introduction
     ❱
 5.  1. 1.1. Getting Started
 6.  2. Common Programming Concepts
     ❱
 7.  1.  2.1. Variables
     2.  2.2. Mutability
     3.  2.3. Comments
     4.  2.4. Types
         ❱
     5.  1. 2.4.1. Tuples
         2. 2.4.2. Records
         3. 2.4.3. Variants
         4. 2.4.4. Immutable Arrays
         5. 2.4.5. Mutable Arrays
     6.  2.5. Operators
         ❱
     7.  1. 2.5.1. Numeric operators
         2. 2.5.2. Relational operators
         3. 2.5.3. Assignment operators
         4. 2.5.4. Text concatenation
         5. 2.5.5. Logical expressions
         6. 2.5.6. Bitwise operators
         7. 2.5.7. Operator precedence
     8.  2.6. Pattern Matching
     9.  2.7. Functions
     10. 2.8. Options and Results
     11. 2.9. Control Flow
         ❱
     12. 1. 2.9.1. If Expression
         2. 2.9.2. If Else Expression
         3. 2.9.3. Switch Expression
     13. 2.10. Objects and Classes
         ❱
     14. 1. 2.10.1. Objects
         2. 2.10.2. Classes
     15. 2.11. Modules and Imports
     16. 2.12. Assertions
 8.  3. Internet Computer Programming Concepts
     ❱
 9.  1. 3.1. Actors
        ❱
     2. 1. 3.1.1. From Actor to Canister
        2. 3.1.2. Canister Calls from Clients
     3. 3.2. Principals and Authentication
     4. 3.3. Async Data
        ❱
     5. 1. 3.3.1. Shared Types
        2. 3.3.2. Candid
     6. 3.4. Basic Memory Persistence
        ❱
     7. 1. 3.4.1. Upgrades
        2. 3.4.2. Stable Variables
 10. 
 11. Part 2
 12. 4. Advanced Types
     ❱
 13. 1. 4.1. Generic Types
     2. 4.2. Subtyping
     3. 4.3. Recursive Types
     4. 4.4. Type Bounds
 14. 5. The Base Library
     ❱
 15. 1. 5.1. Primitive Types
        ❱
     2. 1.  5.1.1. Bool
        2.  5.1.2. Nat
        3.  5.1.3. Int
        4.  5.1.4. Float
        5.  5.1.5. Principal
        6.  5.1.6. Text
        7.  5.1.7. Char
        8.  5.1.8. Bounded Number Types
            ❱
        9.  1. 5.1.8.1. Nat8
            2. 5.1.8.2. Nat16
            3. 5.1.8.3. Nat32
            4. 5.1.8.4. Nat64
            5. 5.1.8.5. Int8
            6. 5.1.8.6. Int16
            7. 5.1.8.7. Int32
            8. 5.1.8.8. Int64
        10. 5.1.9. Blob
     3. 5.2. Utility Modules
        ❱
     4. 1. 5.2.1. Iterators
        2. 5.2.2. Hash
        3. 5.2.3. Option
        4. 5.2.4. Result
        5. 5.2.5. Order
        6. 5.2.6. Error
        7. 5.2.7. Debug
     5. 5.3. Data Structures
        ❱
     6. 1. 5.3.1. Array
        2. 5.3.2. List
        3. 5.3.3. Buffer
        4. 5.3.4. HashMap
        5. 5.3.5. RBTree
     7. 5.4. More Data Structures
     8. 5.5. IC APIs
        ❱
     9. 1. 5.5.1. Time
        2. 5.5.2. Timer
        3. 5.5.3. CertifiedData
        4. 5.5.4. Random
        5. 5.5.5. Experimental
 16. 6. Advanced Concepts
     ❱
 17. 1. 6.1. Async Programming
     2. 6.2. Scalability
        ❱
     3. 1. 6.2.1. Actor Classes
        2. 6.2.2. Stable Storage
     4. 6.3. System API's
        ❱
     5. 1. 6.3.1. Message Inspection
        2. 6.3.2. Timers
        3. 6.3.3. Certified Variables
        4. 6.3.4. Pre-upgrade and Post-upgrade
        5. 6.3.5. Cryptographic Randomness
 18. 
 19. Part 3
 20. 7. Project Deployment
     ❱
 21. 1. 7.1. Installing the SDK
     2. 7.2. Local Deployment
     3. 7.3. Canister Status
     4. 7.4. Identities and PEM Files
     5. 7.5. Cycles and ICP
     6. 7.6. Cycles Wallet
     7. 7.7. IC Deployment
 22. 8. Common Internet Computer Canisters
     ❱
 23. 1. 8.1. IC Management Canister
     2. 8.2. ICP Ledger Canister
     3. 8.3. Cycle Minting Canister
 24. 9. Internet Computer Standards
     ❱
 25. 1. 9.1. ICRC1
 26. 10. Tokenized Comments Example
 27. 
 28. APPENDIX
 29. 11. TABLES


 * Light
 * Rust
 * Coal
 * Navy
 * Ayu


THE MOTOKO PROGRAMMING LANGUAGE BOOK





ACTOR CLASSES

In the same way classes are constructor functions for objects, similarly actor
classes are constructors for actors. An actor class is like a template for
programmatically creating actors of a specific actor type.

But unlike ordinary public classes (that are usually declared inside a module),
a single actor class is written in its own separate .mo file and is imported
like a module.

See 'Actor classes' and 'Actor classes generalize actors' in the official
documentation for more information.

> NOTE
> For programmatically managing actor classes, also check out Actor class
> management


A SIMPLE ACTOR CLASS

// Actor class in separate source file `actor-class.mo`
actor class User(username : Text) {
    var name = username;

    public query func getName() : async Text { name };

    public func setName(newName : Text) : async () {
        name := newName
    };
};


We use the actor class keywords followed by a name with parentheses User() and
an optional input argument list, in this case username : Text.

The body of the actor class, like any actor, may contain variables, private or
shared functions, type declarations, private async* functions, etc.


ACTOR CLASS IMPORT

We import the actor class like we would import a module.

// `main.mo`
import User "actor-class";


We import the actor class from actor-class.mo, a file in the same directory. We
chose the name User for the module to represent the actor class.

The module type of User now looks like this:

module {
    type User = {
        getName: query () -> async Text;
        setName: Text -> async ();
    };
    User : (Text) -> async User;
};


This module contains two fields:

 1. The type of the actor that we can 'spin up' with this actor class.
    In this case, the actor type User consists of two shared functions, one of
    which is a query function.

 2. The constructor function that creates an actor of this type.
    The function User takes a Text argument and returns a future async User for
    an actor of type User.

> NOTE
> In the module above, the name User is used as the name of a type and a
> function, see imports. The line User : (Text) -> async User; first uses the
> name User as function name and then as a type name in async User.


INSTALLING AN INSTANCE OF AN ACTOR CLASS

The function User : (Text) -> async User can be called and awaited from an
asynchronous context from within a running actor. Lets refer to this actor as
the Parent actor.

The await for User initiates an install of a new instance of the actor class as
a new 'Child' actor running on the IC.

let instance = await User.User("Alice");


A new canister is created with the Parent actor as the single controller of the
new canister.

The await yields an actor actor {} with actor type User. This actor can be
stored locally in the Parent actor and used as a reference to interact with the
Child actor.


MULTI-CANISTER SCALING

Whenever you need to scale up your application to multiple actors (running in
multiple canisters), you could use actor classes to repeatedly install new
instances of the same actor class.

// `main.mo`
import Principal "mo:base/Principal";
import Buffer "mo:base/Buffer";

import User "actor-class";

actor {
    let users = Buffer.Buffer<User.User>(1);

    public func newUser(name : Text) : async Principal {
        let instance = await User.User(name);
        users.add(instance);

        Principal.fromActor(instance);
    };
};


This actor declares a Buffer of type Buffer<User.User> with User.User (our actor
type from our actor class module) as a generic type parameter. The buffer is
named users and has initial capacity of 1. We can use this buffer to store
instances of newly created actors from our actor class.

The shared function newUser takes a Text and uses that as the argument to await
the function User.User. This yields a new actor named instance.

We add the new actor to the buffer (users.add(instance)) to be able to interact
with it later.

Finally, we return the principal of the new actor by calling
Principal.fromActor(instance).

> NOTE
> On the IC we actually need to provide some cycles with the call to the actor
> constructor User.User(). On Motoko Playground, this code may work fine for
> testing purposes.


CALLING CHILD ACTORS

The last example is not very useful in practice, because we can't interact with
the actors after they are installed. Lets add some functionality that allows us
to call the shared functions of our Child actors.

// `main.mo`
import Principal "mo:base/Principal";
import Buffer "mo:base/Buffer";
import Error "mo:base/Error";

import User "actor-class";

actor {
    let users = Buffer.Buffer<User.User>(1);

    public func newUser(name : Text) : async Principal {
        let instance = await User.User(name);
        users.add(instance);

        Principal.fromActor(instance);
    };

    public func readName(index : Nat) : async Text {
        switch (users.getOpt(index)) {
            case (?user) { await user.getName() };
            case (null) { throw (Error.reject "No user at index") };
        };
    };

    public func writeName(index : Nat, newName : Text) : async () {
        switch (users.getOpt(index)) {
            case (?user) { await user.setName(newName) };
            case (null) { throw (Error.reject "No user at index") };
        };
    };
};


We added two shared functions readName and writeName. They both take a Nat to
index into the users buffer. They use the getOpt function ('method' from the
Buffer Class) in a switch expression to test whether an actor exists at that
index in the buffer.

If an actor exists at that index, we bind the name user to the actor instance
and so we can call and await the shared functions of the Child actor by
referring to them like user.getName(). Otherwise, we throw an Error.