motoko
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMotoko Language
Motoko语言
What This Is
内容说明
Motoko is the native programming language for Internet Computer canisters. It has actor-based concurrency, built-in persistence (orthogonal persistence), and a type system designed for safe canister upgrades. This skill covers the syntax and type system pitfalls that cause compilation errors or runtime traps when generating Motoko code.
Motoko是Internet Computer容器(canister)的原生编程语言,它基于actor模型实现并发,内置持久化(正交持久化)特性,其类型系统专为canister的安全升级设计。本指南涵盖了编写Motoko代码时会导致编译错误或运行时陷阱的语法及类型系统常见问题。
Prerequisites
前置条件
mops.toml at the project root:
toml
[toolchain]
moc = "1.3.0"
[dependencies]
core = "2.3.1"moc@dfinity/motokoicp buildnpm i -g ic-mops项目根目录下需存在mops.toml文件:
toml
[toolchain]
moc = "1.3.0"
[dependencies]
core = "2.3.1"moc@dfinity/motokoicp buildnpm i -g ic-mopsCompilation Error Pitfalls
编译错误常见陷阱
-
Writinginstead of
actor. Since moc 0.15.0, thepersistent actorkeyword is mandatory on all actors and actor classes. Plainpersistentproduces error M0220.actormotoko// 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; }; -
Putting type declarations before the actor. Onlystatements are allowed before
import. All type definitions,persistent actor, andletdeclarations must go inside the actor body. Violation produces error M0141.varmotoko// 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; }; -
Usingkeyword in persistent actors. In
stable, allpersistent actorandletdeclarations are stable by default. Writingvaris redundant and produces warning M0218. Usestable varfor data that should reset on upgrade.transient varmotokopersistent 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; };The old keywordwas renamed toflexiblein moc 0.13.5. Never usetransient.flexible -
Using HashMap, Buffer, TrieMap, or RBTree in persistent actors. These types from the oldlibrary contain closures and are NOT stable. Using them in a
baseproduces error M0131. They do not exist inpersistent actor— the modern standard library has stable replacements.mo:coremotoko// 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) -
Reassigningbindings.
letis immutable in Motoko — there is no reassignment. Useletfor mutable values.varmotoko// 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 place -
Usingor
continuewithout labels (moc < 1.2.0). Unlabeledbreakandbreakin loops require moc >= 1.2.0. For older compilers, or when targeting an outer loop, use labeled loops.continuemotoko// 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; }; }; -
Shared function parameter types must be shared. All parameters and return types ofactor functions must be shared types. Closures, mutable records,
public, andErrorare NOT shared types. Error codes: M0031, M0032, M0033.async*motoko// 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 () { }; -
Incomplete pattern matches. Switch expressions must cover all possible values. Missing cases produce error M0145.motoko
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"; } }; -
Variant tag argument precedence. Variant constructors with arguments bind tightly. When passing a complex expression, use parentheses to avoid unexpected parsing.motoko
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) -
Usingblocks incorrectly. The
do ? { }operator (null break) only works inside a!block. Using it outside produces error M0064.do ? { }motoko// 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 } };
-
使用而非
actor自moc 0.15.0版本起,所有actor及actor类必须添加persistent actor关键字。仅使用persistent会触发M0220错误。actormotoko// 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; }; -
在actor前放置类型声明 仅语句可置于
import之前。所有类型定义、persistent actor及let声明必须放在actor主体内部。违反此规则会触发M0141错误。varmotoko// 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; }; -
在持久化actor中使用关键字 在
stable中,所有persistent actor和let声明默认都是稳定的。编写var属于冗余操作,会触发M0218警告。对于升级时需要重置的数据,请使用stable var。transient varmotokopersistent 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; };旧关键字在moc 0.13.5版本中已更名为flexible,请勿再使用transient。flexible -
在持久化actor中使用HashMap、Buffer、TrieMap或RBTree 这些来自旧版库的类型包含闭包,不具备稳定性。在
base中使用它们会触发M0131错误。这些类型在persistent actor中已不存在——现代标准库提供了稳定的替代类型。mo:coremotoko// 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) -
重新赋值绑定 在Motoko中,
let是不可变的——不支持重新赋值。如需可变值,请使用let。varmotoko// 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 place -
无标签使用或
continue(moc < 1.2.0版本) 循环中无标签的break和break需要moc版本≥1.2.0。对于旧版编译器或需要跳出外层循环的场景,请使用带标签的循环。continuemotoko// 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; }; }; -
共享函数的参数类型必须为共享类型actor函数的所有参数和返回值必须是共享类型。闭包、可变记录、
public及Error均不属于共享类型。错误代码:M0031、M0032、M0033。async*motoko// 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 () { }; -
模式匹配不完整 Switch表达式必须覆盖所有可能的值。缺失分支会触发M0145错误。motoko
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"; } }; -
变体标签参数的优先级 带参数的变体构造函数绑定优先级较高。传递复杂表达式时,请使用括号避免解析歧义。motoko
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 ? { }运算符(空值中断)仅能在!块内使用。在块外使用会触发M0064错误。do ? { }motoko// 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 } };
mo:core Standard Library
mo:core标准库
The library (package name on mops) is the modern standard library. It replaces the deprecated library. Minimum moc version: 1.0.0.
corecorebasecorecorebaseImport pattern
导入方式
Always import from , never from :
mo:core/mo:base/motoko
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";请始终从导入,切勿从导入:
mo:core/mo:base/motoko
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";Available modules
可用模块
Array, Base64, Blob, Bool, CertifiedData, Char, Cycles, Debug, Error, Float, Func, Int, Int8, Int16, Int32, Int64, InternetComputer, Iter, List, Map, Nat, Nat8, Nat16, Nat32, Nat64, Option, Order, Principal, PriorityQueue, Queue, Random, Region, Result, Runtime, Set, Stack, Text, Time, Timer, Tuples, Types, VarArray, WeakReference.
Array、Base64、Blob、Bool、CertifiedData、Char、Cycles、Debug、Error、Float、Func、Int、Int8、Int16、Int32、Int64、InternetComputer、Iter、List、Map、Nat、Nat8、Nat16、Nat32、Nat64、Option、Order、Principal、PriorityQueue、Queue、Random、Region、Result、Runtime、Set、Stack、Text、Time、Timer、Tuples、Types、VarArray、WeakReference。
Key collection APIs
核心集合API
Map (B-tree, , stable):
O(log n)motoko
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);
};
};Set (B-tree, , stable):
O(log n)motoko
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"); // trueList (growable, stable):
motoko
import 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 boundsNote: returns (safe). returns and traps on out-of-bounds. In core < 1.0.0 the names were different ( was , was ).
List.get?TList.atTgetgetOptatgetMap(B树结构,复杂度,稳定):
O(log n)motoko
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);
};
};Set(B树结构,复杂度,稳定):
O(log n)motoko
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"); // trueList(可增长列表,稳定):
motoko
import 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 bounds注意:返回(安全),返回,若索引越界会触发陷阱。在core 1.0.0之前的版本中,两者名称不同(曾为,曾为)。
List.get?TList.atTgetgetOptatgetText.join parameter order
Text.join的参数顺序
motoko
import Text "mo:core/Text";
// First parameter is the iterator, second is the separator
let result = Text.join(["a", "b", "c"].vals(), ", "); // "a, b, c"motoko
import Text "mo:core/Text";
// First parameter is the iterator, second is the separator
let result = Text.join(["a", "b", "c"].vals(), ", "); // "a, b, c"Type parameters often need explicit annotation
类型参数通常需要显式注解
Invariant type parameters cannot always be inferred. When the compiler says "add explicit type instantiation", provide type arguments:
motoko
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);不变类型参数并非总能被自动推断。当编译器提示“add explicit type instantiation”时,请提供类型参数:
motoko
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);Common Patterns
常见模式
Actor with Map and query methods
包含Map与查询方法的Actor
motoko
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))
};
};motoko
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))
};
};Option handling with switch
使用switch处理Option类型
motoko
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" };
}
};motoko
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" };
}
};Error handling with try/catch
使用try/catch处理错误
motoko
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))
}
};motoko
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))
}
};Reading canister environment variables
读取Canister环境变量
motoko
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");motoko
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");