Loading...
Loading...
Motoko language pitfalls and modern syntax for the Internet Computer. Covers persistent actor requirements, stable types, mo:core standard library, type system rules, and common compilation errors. Use when writing Motoko canister code, fixing Motoko compiler errors, or generating Motoko actors. Do NOT use for deployment, icp.yaml config, or CLI commands — use icp-cli instead. Do NOT use for upgrade persistence patterns — use stable-memory instead.
npx skill4agent add dfinity/icskills motoko[toolchain]
moc = "1.3.0"
[dependencies]
core = "2.3.1"moc@dfinity/motokoicp buildnpm i -g ic-mopsactorpersistent actorpersistentactor// Wrong — error M0220: this actor or actor class should be declared `persistent`
actor {
var count : Nat = 0;
};
// Correct
persistent actor {
var count : Nat = 0;
};
// Actor classes also require it
persistent actor class Counter(init : Nat) {
var count : Nat = init;
};importpersistent actorletvar// Wrong — error M0141: move these declarations into the body of the main actor or actor class
import Nat "mo:core/Nat";
type UserId = Nat;
let MAX = 100;
persistent actor { };
// Correct
import Nat "mo:core/Nat";
persistent actor {
type UserId = Nat;
let MAX = 100;
};stablepersistent actorletvarstable vartransient varpersistent actor {
// Wrong — warning M0218: redundant `stable` keyword
stable var count : Nat = 0;
// Correct — implicitly stable
var count : Nat = 0;
// Correct — resets to 0 on every upgrade
transient var requestCount : Nat = 0;
};flexibletransientflexiblebasepersistent actormo:core// Wrong — these types do not exist in mo:core and are not stable
import HashMap "mo:base/HashMap";
import Buffer "mo:base/Buffer";
// Correct — use mo:core stable collections
import Map "mo:core/Map"; // key-value map (B-tree, stable)
import Set "mo:core/Set"; // set (B-tree, stable)
import List "mo:core/List"; // growable list (stable)
import Queue "mo:core/Queue"; // FIFO queue (stable)letletvar// Wrong — cannot assign to immutable let binding
let count = 0;
count := 1;
// Correct
var count = 0;
count := 1;
// Also correct — let is fine for collections (they mutate internally)
let users = Map.empty<Nat, Text>();
Map.add(users, Nat.compare, 0, "Alice"); // mutates the map in placecontinuebreakbreakcontinue// Works since moc 1.2.0
for (x in items.vals()) {
if (x == 0) continue;
};
// Required for moc < 1.2.0, or to target a specific loop
label outer for (x in items.vals()) {
label inner for (y in other.vals()) {
if (y == 0) continue inner;
if (x == y) break outer;
};
};publicErrorasync*// Wrong — functions are not shared types
public func register(callback : () -> ()) : async () { };
// Wrong — mutable records are not shared types
public func store(data : { var count : Nat }) : async () { };
// Correct — use immutable records and avoid closures
public func store(data : { count : Nat }) : async () { };type Color = { #red; #green; #blue };
// Wrong — error M0145: pattern does not cover value #blue
func name(c : Color) : Text {
switch (c) {
case (#red) "Red";
case (#green) "Green";
}
};
// Correct — cover all cases (or use a wildcard)
func name(c : Color) : Text {
switch (c) {
case (#red) "Red";
case (#green) "Green";
case (#blue) "Blue";
}
};type Action = { #transfer : Nat; #none };
// Potentially confusing — what does this parse as?
let a = #transfer 1 + 2; // parsed as (#transfer(1)) + 2 — type error
// Clear — use parentheses for complex expressions
let a = #transfer(1 + 2); // #transfer(3)do ? { }!do ? { }// Wrong — error M0064: misplaced '!' (no enclosing 'do ? { ... }' expression)
func getName(map : Map.Map<Nat, Text>, id : Nat) : ?Text {
let name = Map.get(map, Nat.compare, id)!;
?name
};
// Correct — wrap in do ? { }
func getName(map : Map.Map<Nat, Text>, id : Nat) : ?Text {
do ? {
let name = Map.get(map, Nat.compare, id)!;
name
}
};corecorebasemo:core/mo:base/import Map "mo:core/Map";
import Set "mo:core/Set";
import List "mo:core/List";
import Nat "mo:core/Nat";
import Text "mo:core/Text";
import Int "mo:core/Int";
import Option "mo:core/Option";
import Result "mo:core/Result";
import Iter "mo:core/Iter";
import Principal "mo:core/Principal";
import Time "mo:core/Time";
import Debug "mo:core/Debug";
import Runtime "mo:core/Runtime";O(log n)import Map "mo:core/Map";
import Nat "mo:core/Nat";
persistent actor {
let users = Map.empty<Nat, Text>();
public func addUser(id : Nat, name : Text) : async () {
Map.add(users, Nat.compare, id, name);
};
public query func getUser(id : Nat) : async ?Text {
Map.get(users, Nat.compare, id)
};
public query func userCount() : async Nat {
Map.size(users)
};
public func removeUser(id : Nat) : async () {
Map.remove(users, Nat.compare, id);
};
};O(log n)import Set "mo:core/Set";
import Text "mo:core/Text";
let tags = Set.empty<Text>();
Set.add(tags, Text.compare, "motoko");
Set.contains(tags, Text.compare, "motoko"); // trueimport List "mo:core/List";
let items = List.empty<Text>();
List.add(items, "first");
List.add(items, "second");
List.get(items, 0); // ?"first" — returns ?T, null if out of bounds
List.at(items, 0); // "first" — returns T, traps if out of boundsList.get?TList.atTgetgetOptatgetimport Text "mo:core/Text";
// First parameter is the iterator, second is the separator
let result = Text.join(["a", "b", "c"].vals(), ", "); // "a, b, c"import VarArray "mo:core/VarArray";
// May fail type inference
let doubled = VarArray.map(arr, func x = x * 2);
// Fix: add explicit type arguments
let doubled = VarArray.map<Nat, Nat>(arr, func x = x * 2);import Map "mo:core/Map";
import Nat "mo:core/Nat";
import Text "mo:core/Text";
import Time "mo:core/Time";
import Iter "mo:core/Iter";
persistent actor {
type Profile = {
name : Text;
bio : Text;
created : Int;
};
let profiles = Map.empty<Nat, Profile>();
var nextId : Nat = 0;
public func createProfile(name : Text, bio : Text) : async Nat {
let id = nextId;
nextId += 1;
Map.add(profiles, Nat.compare, id, {
name;
bio;
created = Time.now();
});
id
};
public query func getProfile(id : Nat) : async ?Profile {
Map.get(profiles, Nat.compare, id)
};
public query func listProfiles() : async [(Nat, Profile)] {
Iter.toArray(Map.entries(profiles))
};
};public query func greetUser(id : Nat) : async Text {
switch (Map.get(profiles, Nat.compare, id)) {
case (?profile) { "Hello, " # profile.name # "!" };
case null { "User not found" };
}
};import Error "mo:core/Error";
// try/catch only works with inter-canister calls (async contexts)
public func safeTransfer(to : Principal, amount : Nat) : async Result.Result<(), Text> {
try {
await remoteCanister.transfer(to, amount);
#ok()
} catch (e) {
#err(Error.message(e))
}
};import Runtime "mo:core/Runtime";
// Injected by icp deploy — available in mo:core >= 2.1.0
// Returns ?Text — null if the variable is not set
let ?backendId = Runtime.envVar("PUBLIC_CANISTER_ID:backend")
else Debug.trap("PUBLIC_CANISTER_ID:backend not set");