Skip to main content

Motoko base to core migration guide

The core package is a new and improved standard library for Motoko, focusing on:

  • AI-friendly design patterns.
  • Familiarity coming from languages such as JavaScript, Python, Java, and Rust.
  • Simplified usage of data structures in stable memory.
  • Consistent naming conventions and parameter ordering.

This page provides a comprehensive guide for migrating from the base Motoko package to the new core package.

Project configuration

Add the following to your mops.toml file to begin using the core package:

[dependencies]
core = "0.0.0" # Check the latest version: https://mops.one/core

If you are migrating an existing project, you can keep the base import and gradually transition to using the new API.

Important considerations

When updating to the core package:

  • All data structures can now be stored in stable memory without the need for pre-upgrade/post-upgrade hooks.
  • range() functions in the core library are now exclusive rather than inclusive! Keep this in mind when replacing Iter.range() with Nat.range().
  • Functions previously named vals() are renamed to values(). This also applies to fields. For example, array.vals() can be replaced with array.values().
  • Hash-based data structures are no longer included in the standard library. It is encouraged to use ordered maps and sets for improved security. In some cases, it won't be possible to fully migrate to core due to removal of some features in base. In these cases, you can continue using both packages side-by-side or search for Mops packages built by the community.

For details on function signatures, please refer to the official documentation.

Also, feel free to ask for help by posting on the ICP developer forum or opening a GitHub issue on the dfinity/motoko-core repository.

Module changes

1. New modules

The following modules are new in the core package:

  • List - Mutable list
  • Map - Mutable map
  • Queue - Mutable double-ended queue
  • Set - Mutable set
  • Runtime - Runtime utilities and assertions
  • Tuples - Tuple utilities
  • Types - Common type definitions
  • VarArray - Mutable array operations
  • pure/List - Immutable list (originally mo:base/List)
  • pure/Map - Immutable map (originally mo:base/OrderedMap)
  • pure/RealTimeQueue - Queue implementation with performance tradeoffs
  • pure/Set - Immutable set (originally mo:base/OrderedSet)

2. Renamed modules

Base moduleCore moduleNotes
ExperimentalCyclesCyclesStabilized module for cycle management
ExperimentalInternetComputerInternetComputerStabilized low-level ICP interface
Dequepure/QueueEnhanced double-ended queue becomes mutable queue
Listpure/ListOriginal immutable list moved to pure/ namespace
OrderedMappure/MapOrdered map moved to pure/ namespace
OrderedSetpure/SetOrdered set moved to pure/ namespace

The last three entries represent the migration of immutable data structures to the pure/ namespace. The core package introduces a clear separation between mutable data structures (root namespace) and purely functional data structures (pure/ namespace).

3. Removed modules

The following modules have been removed in the core package:

  • AssocList - Use Map or pure/Map instead
  • Buffer - Use List or VarArray instead
  • ExperimentalStableMemory - Deprecated
  • Hash - Vulnerable to hash collision attacks
  • HashMap - Use Map or pure/Map
  • Heap
  • IterType - Merged into Types module
  • None
  • Prelude - Merged into Debug and Runtime
  • RBTree
  • Trie
  • TrieMap - Use Map or pure/Map instead
  • TrieSet - Use Set or pure/Set instead

Modules like Random, Region, Time, Timer, and Stack still exist in core but with modified APIs.

Data structure improvements

The core package introduces a fundamental reorganization of data structures with a clear separation between mutable and immutable (purely functional) APIs. All data structures are now usable in stable memory.

StructureModuleDescription
ListListMutable list
MapMapMutable map
QueueQueueMutable queue (evolved from mo:base/Deque)
SetSetMutable set
ArrayArrayImmutable array
VarArrayVarArrayMutable array
Listpure/ListImmutable list (originally mo:base/List)
Mappure/MapImmutable map (originally mo:base/OrderedMap)
Setpure/SetImmutable set (originally mo:base/OrderedSet)
Queuepure/QueueImmutable queue
RealTimeQueuepure/RealTimeQueueReal-time queue with constant-time operations

Interface changes by module

Array

Renamed functions

  • append()concat()
  • chain()flatMap()
  • freeze()fromVarArray()
  • init()repeat() with reversed argument order
  • make()singleton()
  • mapFilter()filterMap()
  • slice()range()
  • subArray()sliceToArray()
  • thaw()toVarArray()
  • vals()values()

New functions

  • all() - Check if all elements satisfy predicate
  • any() - Check if any element satisfies predicate
  • compare() - Compare two arrays
  • empty() - Create empty array
  • enumerate() - Get indexed iterator
  • findIndex() - Find index of first matching element
  • forEach() - Apply function to each element
  • fromIter() - Create array from iterator
  • isEmpty() - Check if array is empty
  • join() - Join arrays from iterator
  • toText() - Convert array to text representation

Removed functions

  • take() - Use sliceToArray() instead
  • sortInPlace() - Use VarArray.sortInPlace() instead
  • tabulateVar() - Use VarArray.tabulate() instead

Blob

Modified functions

  • fromArrayMut()fromVarArray()
  • hash() - Return type changed from Nat32 to Types.Hash
  • toArrayMut()toVarArray()

New functions

  • empty() - Create an empty blob ("" : Blob)
  • isEmpty() - Check if blob is empty
  • size() - Get number of bytes in a blob (equivalent to blob.size())

Bool

Renamed functions

  • logand()logicalAnd()
  • lognot()logicalNot()
  • logor()logicalOr()
  • logxor()logicalXor()

New functions

  • allValues() - Iterator over all boolean values

Char

Renamed functions

  • isLowercase()isLower()
  • isUppercase()isUpper()

Debug

Added functions

  • todo() - Replaces Prelude.nyi()

Removed functions

  • trap() - Moved to Runtime.trap()

Float

Modified functions

  • equal() - Now requires epsilon parameter
  • notEqual() - Now requires epsilon parameter

Removed functions

  • equalWithin(), notEqualWithin() - Use equal() and notEqual() with epsilon

Iter

Iter.range() has been removed in favor of type-specific range functions such as Nat.range(), Int.range(), Nat32.range(), etc. These functions have an exclusive upper bound, in contrast to the original inclusive upper bound of Iter.range().

import Int "mo:base/Int";
import Debug "mo:base/Debug";

persistent actor {
// Iterate through -3, -2, -1, 0, 1, 2 (exclusive upper bound)
for (number in Int.range(-3, 3)) {
Debug.print(debug_show number);
};

// Iterate through -3, -2, -1, 0, 1, 2, 3
for (number in Int.rangeInclusive(-3, 3)) {
Debug.print(debug_show number);
};
}

rangeInclusive() is included for use cases with an inclusive upper bound. The original Iter.range() corresponds to Nat.rangeInclusive().

Helper functions have been added, such as allValues(), for each finite type in the base package.

Int

New functions

  • fromNat() - Convert Nat to Int
  • fromText() - Parse Int from text
  • range() - Create iterator over range
  • rangeBy() - Create iterator with step
  • rangeByInclusive() - Inclusive range with step
  • rangeInclusive() - Inclusive range
  • toNat() - Convert Int to Nat (safe conversion)

Removed functions

  • hash()
  • hashAcc()

Nat

New functions

  • allValues() - Iterator over all natural numbers
  • bitshiftLeft() / bitshiftRight() - Bit shifting operations
  • fromInt() - Safe conversion from Int
  • fromText() - Parse Nat from text
  • range(), rangeInclusive() - Range iterators
  • rangeBy(), rangeByInclusive() - Range with step
  • toInt() - Convert to Int

Int8, Int16, Int32, Int64, Nat8, Nat16, Nat32, Nat64

Renamed fields

  • maximumValuemaxValue
  • minimumValueminValue

New functions

  • allValues() - Iterator over all values in range
  • range(), rangeInclusive() - Range iterators (replaces Iter.range())
  • explode() - Slice into constituent bytes (only for sizes 16, 32, 64)

Option

Renamed functions

  • make()some() - Create option from value
  • iterate()forEach() - Apply function to option value

New functions

  • compare() - Compare two options
  • toText() - Convert option to text representation

Removed functions

  • assertNull() - Removed in favor of pattern matching
  • assertSome() - Removed in favor of pattern matching

Order

New functions

  • allValues() - Iterator over all order values (#less, #equal, #greater)

Random

The Random module has been completely redesigned in the core package with a new API that provides better control over random number generation and supports both pseudo-random and cryptographic random number generation.

import Random "mo:core/Random";

persistent actor {
transient let random = Random.crypto();

public func main() : async () {
let coin = await* random.bool(); // true or false
let byte = await* random.nat8(); // 0 to 255
let number = await* random.nat64(); // 0 to 2^64
let numberInRange = await* random.natRange(0, 10); // 0 to 9
}
}

New classes

  • Random - Synchronous pseudo-random number generator for simulations and testing
  • AsyncRandom - Asynchronous cryptographic random number generator using ICP entropy

Class methods

  • bool() - Random choice between true and false
  • nat8() - Random Nat8 value in range [0, 256)
  • nat64() - Random Nat64 value in range [0, 2^64)
  • nat64Range(from, to) - Random Nat64 in range [from, to)
  • natRange(from, to) - Random Nat in range [from, to)
  • intRange(from, to) - Random Int in range [from, to)

New functions

  • emptyState() - Initialize empty random number generator state
  • seedState() - Initialize pseudo-random state with 64-bit seed
  • seed() - Create pseudo-random generator from seed
  • seedFromState() - Create pseudo-random generator from state
  • crypto() - Create cryptographic random generator using ICP entropy
  • cryptoFromState() - Create cryptographic generator from state

Result

New functions

  • all() - Check all results in iterator
  • any() - Check any result satisfies predicate
  • forOk() - Apply function to #ok value
  • forErr() - Apply function to #err value
  • fromBool() - Create Result from boolean

Text

Renamed functions

  • toLowercase()toLower()
  • toUppercase()toUpper()
  • translate()flatMap()

New functions

  • isEmpty() - Check if text is empty
  • reverse() - Swap the order of characters
  • toText() - Identity function

Removed functions

  • hash()
  • fromList() - Use fromIter() with list iterator instead
  • toList() - Use toIter() and convert to list if needed

Data structure migration examples

Buffer

Original (base)

import Buffer "mo:base/Buffer";

actor {
type Item = Text;

stable var items : [Item] = [];
let buffer = Buffer.fromArray<Item>(items);

system func preupgrade() {
items := Buffer.toArray(buffer);
};

system func postupgrade() {
items := [];
};

public func add(item : Item) : async () {
buffer.add(item);
};

public query func getItems() : async [Item] {
Buffer.toArray(buffer);
};
};

Updated (core)

import List "mo:core/List";

(
with migration = func(
state : {
var items : [App.Item];
}
) : {
list : List.List<App.Item>;
} = {
list = List.fromArray(state.items);
}
)
actor App {
public type Item = Text; // `public` for migration

stable let list = List.empty<Item>();

public func add(item : Item) : async () {
List.add(list, item);
};

public query func getItems() : async [Item] {
List.toArray(list);
};
};

Deque

Original (base)

import Deque "mo:base/Deque";

actor {
type Item = Text;

stable var deque = Deque.empty<Item>();

public func put(item : Item) : async () {
deque := Deque.pushBack(deque, item);
};

public func take() : async ?Item {
switch (Deque.popFront(deque)) {
case (?(item, newDeque)) {
deque := newDeque;
?item;
};
case null { null };
};
};
};

Updated (core)

import Deque "mo:base/Deque"; // For migration
import Queue "mo:core/Queue";

(
with migration = func(
state : {
var deque : Deque.Deque<App.Item>;
}
) : {
queue : Queue.Queue<App.Item>;
} {
let queue = Queue.empty<App.Item>();
label l loop {
switch (Deque.popFront(state.deque)) {
case (?(item, deque)) {
Queue.pushBack(queue, item);
state.deque := deque;
};
case null {
break l;
};
};
};
{ queue };
}
) actor App {
public type Item = Text; // `public` for migration

stable let queue = Queue.empty<Item>();

public func put(item : Item) : async () {
Queue.pushBack(queue, item);
};

public func take() : async ?Item {
Queue.popFront(queue);
};
};

HashMap

Original (base)

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

actor {
stable var mapEntries : [(Text, Nat)] = [];
let map = HashMap.fromIter<Text, Nat>(mapEntries.vals(), 10, Text.equal, Text.hash);

system func preupgrade() {
mapEntries := Iter.toArray(map.entries());
};

system func postupgrade() {
mapEntries := [];
};

public func update(key : Text, value : Nat) : async () {
map.put(key, value);
};

public func remove(key : Text) : async ?Nat {
map.remove(key);
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(map.entries());
};
};

Updated (core)

import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";

(
with migration = func(
state : {
var mapEntries : [(Text, Nat)];
}
) : {
map : Map.Map<Text, Nat>;
} = {
map = Map.fromIter(state.mapEntries.vals(), Text.compare);
}
)
actor {
stable let map = Map.empty<Text, Nat>();

public func update(key : Text, value : Nat) : async () {
Map.add(map, Text.compare, key, value);
};

public func remove(key : Text) : async ?Nat {
Map.take(map, Text.compare, key);
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(Map.entries(map));
};
};

OrderedMap

Original (base)

import OrderedMap "mo:base/OrderedMap";
import Text "mo:base/Text";
import Iter "mo:base/Iter";

actor {
let textMap = OrderedMap.Make<Text>(Text.compare);
stable var map = textMap.empty<Nat>();

public func update(key : Text, value : Nat) : async () {
map := textMap.put(map, key, value);
};

public func remove(key : Text) : async ?Nat {
let (newMap, removedValue) = textMap.remove(map, key);
map := newMap;
removedValue;
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(textMap.entries(map));
};
};

Updated (core)

import OrderedMap "mo:base/OrderedMap"; // For migration
import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";

(
with migration = func(
state : {
var map : OrderedMap.Map<Text, Nat>;
}
) : {
map : Map.Map<Text, Nat>;
} {
let compare = Text.compare;
let textMap = OrderedMap.Make<Text>(compare);
let map = Map.fromIter<Text, Nat>(textMap.entries(state.map), compare);
{ map };
}
)
actor {
stable let map = Map.empty<Text, Nat>();

public func update(key : Text, value : Nat) : async () {
Map.add(map, Text.compare, key, value);
};

public func remove(key : Text) : async ?Nat {
Map.take(map, Text.compare, key);
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(Map.entries(map));
};
};

OrderedSet

Original (base)

import OrderedSet "mo:base/OrderedSet";
import Text "mo:base/Text";
import Iter "mo:base/Iter";

actor {
type Item = Text;

let textSet = OrderedSet.Make<Item>(Text.compare);
stable var set = textSet.empty();

public func add(item : Item) : async () {
set := textSet.put(set, item);
};

public func remove(item : Item) : async Bool {
let oldSize = textSet.size(set);
set := textSet.delete(set, item);
oldSize > textSet.size(set);
};

public query func getItems() : async [Item] {
Iter.toArray(textSet.vals(set));
};
};

Updated (core)

import OrderedSet "mo:base/OrderedSet"; // For migration
import Set "mo:core/Set";
import Text "mo:core/Text";
import Iter "mo:core/Iter";

(
with migration = func(
state : {
var set : OrderedSet.Set<App.Item>;
}
) : {
set : Set.Set<App.Item>;
} {
let compare = Text.compare;
let textSet = OrderedSet.Make<App.Item>(compare);
let set = Set.fromIter(textSet.vals(state.set), compare);
{ set };
}
)
actor App {
public type Item = Text; // `public` for migration

stable let set = Set.empty<Item>();

public func add(item : Item) : async () {
Set.add(set, Text.compare, item);
};

public func remove(item : Item) : async Bool {
Set.delete(set, Text.compare, item);
};

public query func getItems() : async [Item] {
Iter.toArray(Set.values(set));
};
};

Trie

Original (base)

import Trie "mo:base/Trie";
import Text "mo:base/Text";
import Iter "mo:base/Iter";

actor {
type Key = Text;
type Value = Nat;

stable var trie : Trie.Trie<Key, Value> = Trie.empty();

public func update(key : Key, value : Value) : async () {
let keyHash = Text.hash(key);
trie := Trie.put(trie, { key = key; hash = keyHash }, Text.equal, value).0;
};

public func remove(key : Key) : async ?Value {
let keyHash = Text.hash(key);
let (newTrie, value) = Trie.remove(trie, { key = key; hash = keyHash }, Text.equal);
trie := newTrie;
value;
};

public query func getItems() : async [(Key, Value)] {
Iter.toArray(Trie.iter(trie));
};
};

Updated (core)

import Trie "mo:base/Trie"; // For migration
import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";

(
with migration = func(
state : {
var trie : Trie.Trie<Text, Nat>;
}
) : {
map : Map.Map<Text, Nat>;
} = {
map = Map.fromIter(Trie.iter(state.trie), Text.compare);
}
)
actor {
stable let map = Map.empty<Text, Nat>();

public func update(key : Text, value : Nat) : async () {
Map.add(map, Text.compare, key, value);
};

public func remove(key : Text) : async ?Nat {
Map.take(map, Text.compare, key);
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(Map.entries(map));
};
};

TrieMap

Original (base)

import TrieMap "mo:base/TrieMap";
import Text "mo:base/Text";
import Iter "mo:base/Iter";

actor {
stable var mapEntries : [(Text, Nat)] = [];
let map = TrieMap.fromEntries<Text, Nat>(mapEntries.vals(), Text.equal, Text.hash);

system func preupgrade() {
mapEntries := Iter.toArray(map.entries());
};

system func postupgrade() {
mapEntries := [];
};

public func update(key : Text, value : Nat) : async () {
map.put(key, value);
};

public func remove(key : Text) : async ?Nat {
map.remove(key);
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(map.entries());
};
};

Updated (core)

import Map "mo:core/Map";
import Text "mo:core/Text";
import Iter "mo:core/Iter";

(
with migration = func(
state : {
var mapEntries : [(Text, Nat)];
}
) : {
map : Map.Map<Text, Nat>;
} = {
map = Map.fromIter(state.mapEntries.values(), Text.compare);
}
)
actor {
stable let map = Map.empty<Text, Nat>();

public func update(key : Text, value : Nat) : async () {
Map.add(map, Text.compare, key, value);
};

public func remove(key : Text) : async ?Nat {
Map.take(map, Text.compare, key);
};

public query func getItems() : async [(Text, Nat)] {
Iter.toArray(Map.entries(map));
};
};

TrieSet

Original (base)

import TrieSet "mo:base/TrieSet";
import Text "mo:base/Text";

actor {
type Item = Text;

stable var set : TrieSet.Set<Item> = TrieSet.empty<Item>();

public func add(item : Item) : async () {
set := TrieSet.put<Item>(set, item, Text.hash(item), Text.equal);
};

public func remove(item : Item) : async Bool {
let contained = TrieSet.mem<Item>(set, item, Text.hash(item), Text.equal);
set := TrieSet.delete<Item>(set, item, Text.hash(item), Text.equal);
contained;
};

public query func getItems() : async [Item] {
TrieSet.toArray(set);
};
};

Updated (core)

import Set "mo:core/Set";
import Text "mo:core/Text";
import Iter "mo:core/Iter";
import TrieSet "mo:base/TrieSet";

(
with migration = func(
state : {
var set : TrieSet.Set<Text>;
}
) : {
set : Set.Set<Text>;
} = {
set = Set.fromIter(TrieSet.toArray(state.set).vals(), Text.compare);
}
)
actor App {
public type Item = Text; // `public` for migration

stable let set = Set.empty<Item>();

public func add(item : Item) : async () {
Set.add(set, Text.compare, item);
};

public func remove(item : Item) : async Bool {
Set.delete(set, Text.compare, item);
};

public query func getItems() : async [Item] {
Iter.toArray(Set.values(set));
};
};