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.