Learning the basics of Motoko
One key feature of the Internet Computer platform is the Motoko programming language. The Motoko programming language is a high-level, general purpose language that is similar to other modern programming languages, but specifically designed to handle asynchronous messaging efficiently.
Although Motoko provides an interpreter that can be used interactively and a standalone compiler, you can write programs and compile your code without using either one directly.
Instead, in most cases, you interact with the programming language indirectly through an editor of your choice and using the dfx
command-line execution tool.
To help you get started using this new language, this section provides a brief overview of the language and describes a few basic conventions for writing programs using Motoko. For more complete information about programming in Motoko, including specific language features and detailed examples, see the Language Reference Guide.
Actors and asynchronous messaging
As you start to explore the Motoko programming language, you should keep in mind that each application consists of an actor that communicates with other actors by passing messages asynchronously.
An actor is a special kind of object that passes its messages in an isolated state. When you define an actor, its messages are processed in sequence but in isolation from on each other. As an example of using actors, consider the following program:
let result1 = await service1.computeAnswer(params);
let result2 = await service2.computeAnswer(params);
finalStep(result1, result2)
In this example, the program makes two requests to two distinct
services.
Each service is implemented internally as an actor (object).
The program waits using the await
keyword to wait for each result value in sequence.
The program then uses the result from each request them in the final step, calling the function finalStep
.
The actor-based programming model is particularly well-suited for writing programs intended to run on the Internet Computer platform. However, you can also write programs in Motoko to run on other platforms and inside of other frameworks. If you are writing programs to run on other platforms, you might not use actor objects or asynchronous messaging at all.
Interface descriptions and Motoko
To support multiple languages and cross-language communication, the Motoko compiler automates the production and consumption of interface descriptions. The Motoko compiler generates the interface descriptions using the type signatures in your Motoko programs and the structure of imported pre-defined interfaces.
Prelude and standard library functions
Like other languages, Motoko includes many common functions in predefined prelude and standard library files that you can import into your programs.
By importing the prelude or standard library, you can use common functions like println
without explicitly defining them in your programs.
For example, you might include a line similar to the following to import list functions from the standard library:
import List "../../motoko/base/list";
Basic language features
This section describes the basic language conventions you need to know for programming in Motoko.
Comments
You can use single-line, multi-line, or nested multi-line comments to include descriptive non-executable text in your code. For example, use comments to add context about a code block as a note to yourself or as information you want to share with anyone reviewing or maintaining your code. Properly-formatted comments are treated as whitespace and ignored by the compiler.
Single-line comments
Single-line comments begin with two forward-slashes (//) and consist of the characters following //
to the end of the same line.
For example, you can have single-line comments above, below, or on the same line as the code to which the comment applies.
// This is a single-line comment on its own line above relevant code
import List = "ListLib";
Multi-line and nested comments
You can also include single-line, multi-line, and nested multi-line comments by starting with a forward-slash followed by an asterisk (/*) and ending with an asterisk followed by a forward-slash (*/). For example:
/*
This is a comment that is split into
multiple lines.
*/
You can also write nested multi-line comments by starting a multi-line comment block, then starting a second multi-line comment within the first block. For example:
/* This is the start of the first multi-line comment block.
/* This is the second, nested multi-line comment. */
This is the end of the first multi-line comment block. */
Programs, declarations, and expressions
Each Motoko program consists of zero or more declarations, followed by an optional expression.
For example, the following snippet consists of two declarations for the variables x
and y
followed by an expression, forming a single program:
let x = 1;
let y = x + 1;
x * y + x;
As illustrated in this example, you use a semi-colon (;) to terminate declarations and expressions. For example, to evaluate a simple equation:
1 + 3; // When executed, the result would be "4 : Nat" value and type
Numbers, text and operators
Motoko supports the following number types:
-
Natural numbers (
Nat
) are positive whole integers. -
Integer numbers (
Int
) are positive, negative, or zero whole number values. -
Floating point numbers (
Float
) are fractional numeric 32-bit or 64-bit values.
Basic arithmetic operators work as you would expect. For example:
1 + 1; // = 2
0.1 + 0.2; // = 0.3
8 - 1; // = 7
10 * 2; // = 20
35 / 5; // = 7
You can use double (") quotes to enclose text. For example:
let name = "Chris"; // let name : Text = "Chris"
Relational operators
You can use the following relational operators in Motoko programs.
Use this | To define this relationship |
---|---|
|
Less than. The operator must be enclosed in whitespace. |
|
Greater than. The operator must be enclosed in whitespace. |
|
Equals. |
|
Not equals. |
|
Less than or equal to. |
|
Greater than or equal to. |
Numeric binary operators
Use this | For this operation |
---|---|
|
Addition. |
|
Subtraction. |
|
Multiplication. |
|
Division. |
|
Modulo. |
|
Exponentiation. |
Bitwise binary operators
Use this | For this operation |
---|---|
|
Bitwise AND. |
|
Bitwise OR. |
|
Exclusive OR. |
|
Shift left. |
|
Shift right. The operator must be preceded by whitespace. |
|
Rotate left. |
|
Rotate right. |
Assignment operators
Use this | For this operation |
---|---|
|
Assignment using an in-place update. |
|
In place addition. |
|
In place subtraction. |
|
In place multiplication. |
|
In place divide. |
|
In place modulo. |
|
In place exponentiation. |
|
In place logical AND. |
|
In place logical OR. |
|
In place exclusive OR. |
|
In place shift left. |
|
In place shift right. |
|
In place rotate left. |
|
In place rotate right. |
|
In place concatenation. |
Variables
Variables enable you to relate static names and types with dynamic values that are present only when a program is executed.
You can declare variable names, and if necessary the data type, using the let
keyword. For example, you can use the following notation to set the variable x
to the natural number 1:
let x : Nat = 1;
In this example, the compiler can infer that the expression 1 has type Nat
, and that x
has the same type. Therefore, you can use the following annotation without changing the meaning of the program:
let x = 1
Primitive values
Motoko supports the following primitive types:
-
Booleans (true, false)
-
Integers (…,-2, -1, 0, 1, 2, …)
-
Natural numbers (0, 1, 2, …)
-
Words (fixed-width numbers)
-
Characters (Unicode code points)
-
Text values (strings of Unicode characters)
Integers and natural numbers do not silently over- or under-flow. Instead, they use representations that grow to accommodate any finite number. There are also fixed-width integers and natural numbers (in 8-, 16-, 32-, and 64-bit) types that trap on arithmetic over- and under-flow. The fixed-width integers are signed. The fixed-width natural numbers are unsigned.
Word values have fixed width. Arithmetic for Word types is performed using modulo 2**N where N is the width of the word type in bits.
Motoko does not allow unchecked, uncaught overflows.
All primitive types are shareable so that they can be sent and received across remote function calls.
Non-primitive values
Building on the primitive values and types above, the language permits user-defined types, and each of the following non-primitive value forms and associated types:
-
Tuples, including the unit value (the “empty tuple”).
-
Arrays, in both immutable and mutable forms.
-
Objects, with named, unordered fields and methods.
-
Variants, with named constructors and optional payload values.
-
Function values, including shareable functions that can be called remotely.
-
Async values, otherwise known as futures or promises, that can be synchronized with using
await
. -
Optional values, that can either be
null
or of the form? v
, for a proper valuev
.
Note that if your function is returning an optional value of type T
, then the function’s return type should be of the form '? T':
prefixing a type with question mark (?) adds the null
value.
For example, you would use the following syntax to indicate that the return value of the type identified by ?Phone
is optional (for example, the return value can be null
as well as a proper phone number):
public query func lookup(name: Name): async ?Phone {
return A.find(book, name, nameEq);
};
Functions
First-class functions support multiple arguments and returns and can be polymorphic.
-
T -> U
-
(T, U) -> (V, W)
-
(x : T, y : U) -> V
-
<A, B>(x : T, y : U) -> (V, W)
Functions can be defined as public
or private
, shared
, or locally-scoped.
Objects and actors
Objects are structural record types with fields that can be mutable or immutable. Objects with immutable fields are shareable.
{var x : Int; color : Color} {x : Int; color: Color}
Actors are restricted objects with the following characteristics:
-
State must be isolated.
-
Public methods are implicitly
shared
. -
All interactions are asynchronous.
Actor objects are marked as actor
with syntax similar to the
following:
actor { private var c = 0; public func inc() { c += 1 }; public func get() : async Int { c } }
The fields of an actor are functions that return either:
-
An empty unit return type
()
which is similar to avoid
return value. -
An
async
return type, which is essentially apromise
to return a future value.
Arrays
You can define immutable or mutable arrays. The assignment syntax you use is the same for both immutable and mutable arrays, but you cannot change immutable arrays after allocation.
The following is an example of a simple, immutable array that holds three natural numbers, and has the type [Nat]
:
let test_array : [Nat] = [1, 2, 3] ;
In general, an immutable array uses square brackets around the type of the array’s elements, which must share a single common type. Because immutable arrays cannot be modified,immutable arrays are safe to send and share.
Unlike immutable arrays, each mutable array in Motoko introduces a private actor state which can be used locally but cannot be used in cases where immutable arrays are expected. Mutable arrays cannot be shared or sent in messages. For more information about working with arrays, see the Motoko Language Reference.
Tuples
A tuple is a data structure that contains a sequence of elements. The number of element in the sequence is fixed, but the elements can consist of different data types. Tuples are often used when you want to have a data structure for an object with specific properties, but you don’t want to create a separate type for it.
The following example defines a tuple for a three-dimensional point using integers to specify locations to represent the point on the x-axis, y-axis, and z-axis and a color.
type Point3D = (Int, Int, Int, Color);
let origin = (0, 0, 0, Color.Red);
let (x, y, z, _) = origin;
func isOrigin(p : Point3D) : Bool {
switch p {
case (0, 0, 0, _) true; // pattern match
case _ false;
}
}
Conditional and switching
Motoko supports all the common constructs for expressing conditions and switching between cases.
For example, you can write if
and if else
conditions with syntax similar to the following:
if <exp>
...
if <exp>
...
else <exp>
...
You can also use switch
and case
syntax similar to the following:
switch <exp> {
case 1
...;
case 2
...;
case 3
...}
Identifiers
Identifiers are alphanumeric. They must start with an uppercase or lowercase letter and can include a combination of uppercase or lowercase letters, numbers, and underscores.
Reserved keywords
The following keywords are reserved for specific purposes and cannot be used as identifiers or for any other purpose in Motoko code:
This keyword | Is reserved for this purpose |
---|---|
actor |
Declare actor identifiers, actor objects and actor classes. |
and |
Boolean conjunction (short-circuiting). |
async |
Declare an asynchronous function or create a future. |
assert |
Assert a Boolean property (trapping when false). |
await |
Await the result of an asynchronous computation. |
break |
Exit from a labelled expression or loop (possibly with a value). |
case |
Case of a preceding |
catch |
Failure branch of a preceding |
class |
Class declaration (actor, object and module classes). |
continue |
Continue with the next iteration of |
debug |
Conditional debug expressions, excluded from |
debug_show |
Display a debug message. |
else |
False branch of a conditional expression. |
false |
Boolean literal value false. |
for |
Iterate over the items of an iterator. |
func |
Declare a name function or anonymous function value. |
if |
Branch on a Boolean value. |
ignore |
Discard the value of an expression. |
import |
Import a source file or other resource as a named module. |
in |
Indicatethe domain of |
module |
Declare a module identifier or module expression. |
not |
Negate a Boolean value. |
null |
The literal value of type |
object |
Declare an object identifier or object expression. |
or |
Boolean disjunction (short-circuiting). |
label |
Label an expression for early exit via |
let |
Bind an identifier (or pattern) to a value. |
loop |
Enter a loop (possibly with a guarded exit). |
private |
Restrict the visibility of a declaration to the enclosing actor, object, module or class. |
public |
Publish a declaration as member(s) of the enclosing actor, object, module or class. |
query |
A modifier on shared functions that return results without modifying state. |
return |
Exit from a function or async block (possibly with a value). |
shared |
Declare a function that can be called remotely. |
switch |
Conditional pattern matching, defined by cases. |
throw |
Exit from an expression with an error. |
true |
A Boolean value returned as the result of a comparison. |
try |
Declare a scoped error handler. |
type |
Declare a type abbreviation. |
var |
Specify a (mutable) variable, field or array that can be updated. |
while |
Enter a guarded loop. |