Skip to main content

Writing Motoko code

The Motoko programming language is a new, modern and type safe language for developers who want to build the next generation of distributed applications on ICP, as it is specifically designed to support the unique features of ICP while providing a familiar, yet robust, programming environment. As a new language, Motoko is constantly evolving with support for new features and other improvements.

The Motoko compiler, documentation and other tooling is open source and released under the Apache 2.0 license. Contributions are welcome.

Actors

A canister smart contract is expressed as a Motoko actor. An actor is an autonomous object that fully encapsulates its state and communicates with other actors only through asynchronous messages.

For example, this code defines a stateful Counter actor.

actor Counter {

var value = 0;

public func inc() : async Nat {
value += 1;
return value;
};
}

Its single public function, inc(), can be invoked by this and other actors, to both update and read the current state of its private field value.

Async messages

On ICP, canisters can communicate with other canisters by sending asynchronous messages. Asynchronous messages are function calls that return a future, and use an await construct that allows you to suspend execution until a future has completed. This simple feature avoids creating a loop of explicit asynchronous callbacks in other languages.

actor Factorial {

var last = 1;

public func next() : async Nat {
last *= await Counter.inc();
return last;
}
};

ignore await Factorial.next();
ignore await Factorial.next();
await Factorial.next();

Modern type system

Motoko has been designed to be intuitive to those familiar with JavaScript and other popular languages, but offers modern features such as sound structural types, generics, variant types, and statically checked pattern matching.

type Tree<T> = {
#leaf : T;
#branch : {left : Tree<T>; right : Tree<T>};
};

func iterTree<T>(tree : Tree<T>, f : T -> ()) {
switch (tree) {
case (#leaf(x)) { f(x) };
case (#branch{left; right}) {
iterTree(left, f);
iterTree(right, f);
};
}
};

// Compute the sum of all leaf nodes in a tree
let tree = #branch { left = #leaf 1; right = #leaf 2 };
var sum = 0;
iterTree<Nat>(tree, func (leaf) { sum += leaf });
sum

Autogenerated IDL files

A Motoko actor always presents a typed interface to its clients as a suite of named functions with argument and result types.

The Motoko compiler and the IC SDK can emit this interface in a language neutral format called Candid. Other canisters, browser resident code, and mobile apps that support Candid can use the actor’s services. The Motoko compiler can consume and produce Candid files, allowing Motoko to seamlessly interact with canisters implemented in other programming languages (provided they support Candid).

For example, the previous Motoko Counter actor has the following Candid interface:

service Counter : {
inc : () -> (nat);
}

Orthogonal persistence

ICP persists the memory and other state of your canister as it executes. The state of a Motoko actor, including its in-memory data structures, survive indefinitely. Actor state does not need to be explicitly restored and saved to external storage.

For example, in the following Registry actor that assigns sequential IDs to textual names, the state of the hash table is preserved across calls, even though the state of the actor is replicated across many ICP node machines and typically not resident in memory:

import Text "mo:base/Text";
import Map "mo:base/HashMap";

actor Registry {

let map = Map.HashMap<Text, Nat>(10, Text.equal, Text.hash);

public func register(name : Text) : async () {
switch (map.get(name)) {
case null {
map.put(name, map.size());
};
case (?_) { };
}
};

public func lookup(name : Text) : async ?Nat {
map.get(name);
};
};

await Registry.register("hello");
(await Registry.lookup("hello"), await Registry.lookup("world"))

Upgrades

Motoko provides numerous features to help you leverage orthogonal persistence, including the ability to retain a canister’s data as you upgrade the code of the canister.

For example, Motoko lets you declare certain variables as stable. These variables are automatically preserved across canister upgrades.

Consider a stable counter:

actor Counter {

stable var value = 0;

public func inc() : async Nat {
value += 1;
return value;
};
}

It can be installed, incremented n times, and then upgraded without interruption:

actor Counter {

stable var value = 0;

public func inc() : async Nat {
value += 1;
return value;
};

public func reset() : async () {
value := 0;
}
}

The value was declared stable, meaning the current state, n, of the service is retained after the upgrade. Counting will continue from n, not restart from 0.

The new interface is compatible with the previous one, allowing existing clients referencing the canister to continue to work. New clients will be able to exploit its upgraded functionality, in this example the additional reset function.

To make it more convenient to declare stable variables, and to prevent missing stable declarations, Motoko allows you to prefix the entire actor with the keyword persistent. In a persistent actor, all declarations are stable by default. Only declarations that are explicitly marked transient will be discarded on upgrade.

persistent actor Counter {

var value = 0; // implicitly stable

transient var invocations = 0; // reset on upgrade

public func inc() : async Nat {
value += 1;
invocations += 1;
value;
};

public func reset() : async () {
value := 0;
};

public func getInvocations() : async Nat {
invocations
}

}

In this example, value is now implicitly stable, while invocations is just a transient temporary declaration that won't survive upgrades: it counts the number of calls to inc since the first installation or last upgrade.

For scenarios that can’t be solved using stable variables alone, Motoko provides user-definable upgrade hooks that run immediately before and after an upgrade, allowing you to migrate arbitrary state to stable variables.

Source code organization

Motoko allows for separating different portions of code out of the main.mo file into separate modules. This can be useful for breaking up large pieces of source code into smaller, more manageable pieces.

One common approach is to exclude type definitions from the main.mo file and instead include them in a Types.mo file.

Another approach is to declare stable variables and public methods in the main.mo file, and then break out all the logic and types into other files. This workflow can be beneficial for efficient unit testing.

Next steps

To start writing Motoko code, start by reading the in-depth documentation for some of the concepts described above:

The Motoko programming language continues to evolve with each release of the IC SDK and with ongoing updates to the Motoko compiler. Check back regularly to try new features and see what’s changed.

Logo