motoko

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motoko 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
must be pinned — the
@dfinity/motoko
recipe resolves the compiler from this field. Without it,
icp build
fails. Install the package manager with
npm i -g ic-mops
.
项目根目录下需存在mops.toml文件:
toml
[toolchain]
moc = "1.3.0"

[dependencies]
core = "2.3.1"
moc
版本必须固定——
@dfinity/motoko
会从该字段解析编译器版本。若未固定版本,
icp build
命令将执行失败。请通过
npm i -g ic-mops
安装包管理器。

Compilation Error Pitfalls

编译错误常见陷阱

  1. Writing
    actor
    instead of
    persistent actor
    .
    Since moc 0.15.0, the
    persistent
    keyword is mandatory on all actors and actor classes. Plain
    actor
    produces error M0220.
    motoko
    // 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;
    };
  2. Putting type declarations before the actor. Only
    import
    statements are allowed before
    persistent actor
    . All type definitions,
    let
    , and
    var
    declarations must go inside the actor body. Violation produces error M0141.
    motoko
    // 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;
    };
  3. Using
    stable
    keyword in persistent actors.
    In
    persistent actor
    , all
    let
    and
    var
    declarations are stable by default. Writing
    stable var
    is redundant and produces warning M0218. Use
    transient var
    for data that should reset on upgrade.
    motoko
    persistent 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 keyword
    flexible
    was renamed to
    transient
    in moc 0.13.5. Never use
    flexible
    .
  4. Using HashMap, Buffer, TrieMap, or RBTree in persistent actors. These types from the old
    base
    library contain closures and are NOT stable. Using them in a
    persistent actor
    produces error M0131. They do not exist in
    mo:core
    — the modern standard library has stable replacements.
    motoko
    // 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)
  5. Reassigning
    let
    bindings.
    let
    is immutable in Motoko — there is no reassignment. Use
    var
    for mutable values.
    motoko
    // 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
  6. Using
    continue
    or
    break
    without labels (moc < 1.2.0).
    Unlabeled
    break
    and
    continue
    in loops require moc >= 1.2.0. For older compilers, or when targeting an outer loop, use labeled loops.
    motoko
    // 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;
      };
    };
  7. Shared function parameter types must be shared. All parameters and return types of
    public
    actor functions must be shared types. Closures, mutable records,
    Error
    , and
    async*
    are NOT shared types. Error codes: M0031, M0032, M0033.
    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 () { };
  8. 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";
      }
    };
  9. 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)
  10. Using
    do ? { }
    blocks incorrectly.
    The
    !
    operator (null break) only works inside a
    do ? { }
    block. Using it outside produces error M0064.
    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
      }
    };
  1. 使用
    actor
    而非
    persistent actor
    自moc 0.15.0版本起,所有actor及actor类必须添加
    persistent
    关键字。仅使用
    actor
    会触发M0220错误。
    motoko
    // 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;
    };
  2. 在actor前放置类型声明
    import
    语句可置于
    persistent actor
    之前。所有类型定义、
    let
    var
    声明必须放在actor主体内部。违反此规则会触发M0141错误。
    motoko
    // 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;
    };
  3. 在持久化actor中使用
    stable
    关键字
    persistent actor
    中,所有
    let
    var
    声明默认都是稳定的。编写
    stable var
    属于冗余操作,会触发M0218警告。对于升级时需要重置的数据,请使用
    transient var
    motoko
    persistent 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;
    };
    旧关键字
    flexible
    在moc 0.13.5版本中已更名为
    transient
    ,请勿再使用
    flexible
  4. 在持久化actor中使用HashMap、Buffer、TrieMap或RBTree 这些来自旧版
    base
    库的类型包含闭包,不具备稳定性。在
    persistent actor
    中使用它们会触发M0131错误。这些类型在
    mo:core
    中已不存在——现代标准库提供了稳定的替代类型。
    motoko
    // 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)
  5. 重新赋值
    let
    绑定
    在Motoko中,
    let
    是不可变的——不支持重新赋值。如需可变值,请使用
    var
    motoko
    // 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
  6. 无标签使用
    continue
    break
    (moc < 1.2.0版本)
    循环中无标签的
    break
    continue
    需要moc版本≥1.2.0。对于旧版编译器或需要跳出外层循环的场景,请使用带标签的循环。
    motoko
    // 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;
      };
    };
  7. 共享函数的参数类型必须为共享类型
    public
    actor函数的所有参数和返回值必须是共享类型。闭包、可变记录、
    Error
    async*
    均不属于共享类型。错误代码:M0031、M0032、M0033。
    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 () { };
  8. 模式匹配不完整 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";
      }
    };
  9. 变体标签参数的优先级 带参数的变体构造函数绑定优先级较高。传递复杂表达式时,请使用括号避免解析歧义。
    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)
  10. 错误使用
    do ? { }
    !
    运算符(空值中断)仅能在
    do ? { }
    块内使用。在块外使用会触发M0064错误。
    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
core
library (package name
core
on mops) is the modern standard library. It replaces the deprecated
base
library. Minimum moc version: 1.0.0.
core
库(在mops上的包名为
core
)是现代标准库,已替代废弃的
base
库。最低支持moc版本:1.0.0。

Import pattern

导入方式

Always import from
mo:core/
, never from
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,
O(log n)
, stable):
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,
O(log n)
, stable):
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"); // true
List (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 bounds
Note:
List.get
returns
?T
(safe).
List.at
returns
T
and traps on out-of-bounds. In core < 1.0.0 the names were different (
get
was
getOpt
,
at
was
get
).
Map(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"); // true
List(可增长列表,稳定):
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
注意:
List.get
返回
?T
(安全),
List.at
返回
T
,若索引越界会触发陷阱。在core 1.0.0之前的版本中,两者名称不同(
get
曾为
getOpt
at
曾为
get
)。

Text.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");