Actors
Actors, like objects, are like a package of state and a public API that accesses and modifies that state. They are declared using the actor
keyword and have an actor type.
Top level actors
Today (Jan 2023), an actor in Motoko is defined as a top-level item in its own Motoko source file. Optionally, it may be preceded by one or more imports:
// actor.mo
import Mod "mod";
actor {
};
We declared an empty actor in its own source file actor.mo
. It is preceded by an import of a module defined in mod.mo
and named Mod
for use inside the actor.
You may feel disappointed by the simplicity of this example, but this setup (one source file containing only imports and one actor) is the core of every Motoko program!
Actors vs. objects
Unlike objects, actors may only have private
(immutable or mutable) variables. We can only communicate with an actor by calling its public shared functions and never by directly modifying its private
variables. In this way, the memory state of an actor is isolated from other actors. Only public shared functions (and some system functions) can change the memory state of an actor.
To understand actors it is useful to compare them with objects:
Public API
- Public functions in actors are accessible from outside the Internet Computer.
- Public functions in objects are only accessible from within your Motoko code.
Private and public variables
- Actors don't allow public (immutable or mutable) variables
- Objects do allow public (immutable or mutable) variables
Private functions
- Private functions in actors are not part of the actor type
- Private functions in objects are not part of the object type
Public shared functions
- Actors only allow shared public functions
- Objects only allow non-shared public functions
Class and Actor Class
- Actors have 'factory' functions called Actor Classes
- Objects have 'factory' functions called Classes
For a full comparison checkout: Motoko Items Comparison Table
Public Shared Functions in Actors
Actors allow three kinds of public functions:
-
Public shared query functions:
Can only read state -
Public shared update functions:
Can read and write state -
Public shared oneway functions:
Can read and write, but don't have any return value.
NOTE
Public shared query functions are fast, but don't have the full security guarantees of the Internet Computer because they do not 'go through' consensus
Shared async types
The argument and return types of public shared functions are restricted to shared types only. We will cover shared types later in this book.
Query and update functions always have the special async
return type.
Oneway functions always immediately return ()
regardless of whether they execute successfully.
These functions are only allowed inside actors. Here are their function signatures and function types.
Public shared query
public shared query func read() : async () { () };
// Has type `shared query () -> async ()`
The function named read
is declared with public shared query
and returns async ()
. This function is not allowed to modify the state of the actor because of the query
keyword. It can only read state and return most types. The shared query
and async
keywords are part of the function type. Query functions are fast.
Public shared update
public shared func write() : async () { () };
// Has type `shared Text -> async ()`
The function named write
is declared with public shared
and also returns async ()
. This function is allowed to modify the state of the actor. There is no other special keyword (like query) to indicate that this is an update function. The shared
and async
keywords are part of the function type. Update functions take 2 seconds per call.
Public shared oneway
public shared func oneway() { () };
// Has type `shared () -> ()`
The function named oneway
is also declared with public shared
but does not have a return type. This function is allowed to modify the state of the actor. Because it has no return type, it is assumed to be a oneway function which always returns ()
regardless of whether they execute successfully. Only the shared
keyword is part of their type. Oneway functions also take 2 seconds per call.
In our example none of the functions take any arguments for simplicity.
NOTE
Theshared
keyword may be left out and Motoko will assume the public function in an actor to beshared
by default. To avoid confusion with public functions elsewhere (like modules, objects or classes) we will keep using the shared keyword for public functions in actors
A simple actor
Here's an actor with one state variable and some functions that read or write that variable:
actor {
private var latestComment = "";
public shared query func readComment() : async Text {
latestComment;
};
public shared func writeComment(comment : Text) : async () {
latestComment := comment;
};
public shared func deleteComment() {
latestComment := "";
};
};
This actor has one private mutable variable named latestComment
of type Text
and is initially set to the empty string ""
. This variable is not visible 'from the outside', but only available internally inside the actor. The actor also demonstrates the three possible public functions in any actor.
Then first function readComment
is a query function that takes no arguments and only reads the state variable latestComment
. It returns async Text
.
The second function writeComment
is an update function that takes one argument and modifies the state variable. It could return some value, but in this case it returns async ()
.
The third function deleteComment
is a oneway function that doesn't take any arguments and also modifies the state. But it can not return any value and always returns ()
regardless of whether it successfully updated the state or not.
Actor type
Only public shared functions are part of the type of an actor.
The type of the actor above is the following:
type CommentActor = actor {
deleteComment : shared () -> ();
readComment : shared query () -> async Text;
writeComment : shared Text -> async ();
};
We named our actor type CommentActor
. The type itself starts with the actor
keyword followed by curly braces {}
(like objects). Inside we find the three function names as fields of the type definition.
Every field name is a public shared function with its own function type. The order doesn't matter, but the Motoko orders them alphabetically.
readComment
has type shared query () -> async Text
indicating it is a shared query function with no arguments and async Text
return type.
writeComment
has type shared Text -> async ()
indicating it is an shared update function that takes one Text
argument and returns no value async ()
.
deleteComment
has type shared () -> ()
indicating it is a shared oneway function that takes no arguments and always returns ()
.