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 thecore
library are now exclusive rather than inclusive! Keep this in mind when replacingIter.range()
withNat.range()
.- Functions previously named
vals()
are renamed tovalues()
. This also applies to fields. For example,array.vals()
can be replaced witharray.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 inbase
. 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 listMap
- Mutable mapQueue
- Mutable double-ended queueSet
- Mutable setRuntime
- Runtime utilities and assertionsTuples
- Tuple utilitiesTypes
- Common type definitionsVarArray
- Mutable array operationspure/List
- Immutable list (originallymo:base/List
)pure/Map
- Immutable map (originallymo:base/OrderedMap
)pure/RealTimeQueue
- Queue implementation with performance tradeoffspure/Set
- Immutable set (originallymo:base/OrderedSet
)
2. Renamed modules
Base module | Core module | Notes |
---|---|---|
ExperimentalCycles | Cycles | Stabilized module for cycle management |
ExperimentalInternetComputer | InternetComputer | Stabilized low-level ICP interface |
Deque | pure/Queue | Enhanced double-ended queue becomes mutable queue |
List | pure/List | Original immutable list moved to pure/ namespace |
OrderedMap | pure/Map | Ordered map moved to pure/ namespace |
OrderedSet | pure/Set | Ordered 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
- UseMap
orpure/Map
insteadBuffer
- UseList
orVarArray
insteadExperimentalStableMemory
- DeprecatedHash
- Vulnerable to hash collision attacksHashMap
- UseMap
orpure/Map
Heap
IterType
- Merged intoTypes
moduleNone
Prelude
- Merged intoDebug
andRuntime
RBTree
Trie
TrieMap
- UseMap
orpure/Map
insteadTrieSet
- UseSet
orpure/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.
Structure | Module | Description |
---|---|---|
List | List | Mutable list |
Map | Map | Mutable map |
Queue | Queue | Mutable queue (evolved from mo:base/Deque ) |
Set | Set | Mutable set |
Array | Array | Immutable array |
VarArray | VarArray | Mutable array |
List | pure/List | Immutable list (originally mo:base/List ) |
Map | pure/Map | Immutable map (originally mo:base/OrderedMap ) |
Set | pure/Set | Immutable set (originally mo:base/OrderedSet ) |
Queue | pure/Queue | Immutable queue |
RealTimeQueue | pure/RealTimeQueue | Real-time queue with constant-time operations |
Interface changes by module
Array
Renamed functions
append()
→concat()
chain()
→flatMap()
freeze()
→fromVarArray()
init()
→repeat()
with reversed argument ordermake()
→singleton()
mapFilter()
→filterMap()
slice()
→range()
subArray()
→sliceToArray()
thaw()
→toVarArray()
vals()
→values()
New functions
all()
- Check if all elements satisfy predicateany()
- Check if any element satisfies predicatecompare()
- Compare two arraysempty()
- Create empty arrayenumerate()
- Get indexed iteratorfindIndex()
- Find index of first matching elementforEach()
- Apply function to each elementfromIter()
- Create array from iteratorisEmpty()
- Check if array is emptyjoin()
- Join arrays from iteratortoText()
- Convert array to text representation
Removed functions
take()
- UsesliceToArray()
insteadsortInPlace()
- UseVarArray.sortInPlace()
insteadtabulateVar()
- UseVarArray.tabulate()
instead
Blob
Modified functions
fromArrayMut()
→fromVarArray()
hash()
- Return type changed fromNat32
toTypes.Hash
toArrayMut()
→toVarArray()
New functions
empty()
- Create an empty blob ("" : Blob
)isEmpty()
- Check if blob is emptysize()
- Get number of bytes in a blob (equivalent toblob.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()
- ReplacesPrelude.nyi()
Removed functions
trap()
- Moved toRuntime.trap()
Float
Modified functions
equal()
- Now requires epsilon parameternotEqual()
- Now requires epsilon parameter
Removed functions
equalWithin()
,notEqualWithin()
- Useequal()
andnotEqual()
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 IntfromText()
- Parse Int from textrange()
- Create iterator over rangerangeBy()
- Create iterator with steprangeByInclusive()
- Inclusive range with steprangeInclusive()
- Inclusive rangetoNat()
- Convert Int to Nat (safe conversion)
Removed functions
hash()
hashAcc()
Nat
New functions
allValues()
- Iterator over all natural numbersbitshiftLeft()
/bitshiftRight()
- Bit shifting operationsfromInt()
- Safe conversion from IntfromText()
- Parse Nat from textrange()
,rangeInclusive()
- Range iteratorsrangeBy()
,rangeByInclusive()
- Range with steptoInt()
- Convert to Int
Int8
, Int16
, Int32
, Int64
, Nat8
, Nat16
, Nat32
, Nat64
Renamed fields
maximumValue
→maxValue
minimumValue
→minValue
New functions
allValues()
- Iterator over all values in rangerange()
,rangeInclusive()
- Range iterators (replacesIter.range()
)explode()
- Slice into constituent bytes (only for sizes16
,32
,64
)
Option
Renamed functions
make()
→some()
- Create option from valueiterate()
→forEach()
- Apply function to option value
New functions
compare()
- Compare two optionstoText()
- Convert option to text representation
Removed functions
assertNull()
- Removed in favor of pattern matchingassertSome()
- 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 testingAsyncRandom
- Asynchronous cryptographic random number generator using ICP entropy
Class methods
bool()
- Random choice betweentrue
andfalse
nat8()
- RandomNat8
value in range[0, 256)
nat64()
- RandomNat64
value in range[0, 2^64)
nat64Range(from, to)
- RandomNat64
in range[from, to)
natRange(from, to)
- RandomNat
in range[from, to)
intRange(from, to)
- RandomInt
in range[from, to)
New functions
emptyState()
- Initialize empty random number generator stateseedState()
- Initialize pseudo-random state with 64-bit seedseed()
- Create pseudo-random generator from seedseedFromState()
- Create pseudo-random generator from statecrypto()
- Create cryptographic random generator using ICP entropycryptoFromState()
- Create cryptographic generator from state
Result
New functions
all()
- Check all results in iteratorany()
- Check any result satisfies predicateforOk()
- Apply function to#ok
valueforErr()
- Apply function to#err
valuefromBool()
- Create Result from boolean
Text
Renamed functions
toLowercase()
→toLower()
toUppercase()
→toUpper()
translate()
→flatMap()
New functions
isEmpty()
- Check if text is emptyreverse()
- Swap the order of characterstoText()
- Identity function
Removed functions
hash()
fromList()
- UsefromIter()
with list iterator insteadtoList()
- UsetoIter()
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));
};
};