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
Section titled “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/coreIf you are migrating an existing project, you can keep the base import and gradually transition to using the new API.
Important considerations
Section titled “Important considerations”Version requirements
The core package depends on new language features, so make sure to update to the latest dfx (0.28+) or Motoko compiler (0.15+) before migrating.
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, provided those data structures are instantiated at stable type arguments.
range()functions in thecorelibrary 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
coredue 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
Section titled “Module changes”1. New modules
Section titled “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
Section titled “2. Renamed modules”| Base package | Core package | Notes |
|---|---|---|
ExperimentalCycles | Cycles | Stabilized module for cycle management |
ExperimentalInternetComputer | InternetComputer | Stabilized low-level ICP interface |
Deque | pure/Queue | Enhanced double-ended queue becomes immutable 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 |
3. Removed modules
Section titled “3. Removed modules”The following modules have been removed in the core package:
AssocList- UseMaporpure/MapinsteadBuffer- UseListorVarArrayinsteadExperimentalStableMemory- DeprecatedHash- Vulnerable to hash collision attacksHashMap- UseMaporpure/MapHeap- UseMaporSetinsteadIterType- Merged intoTypesmoduleNone- Useswitch x {}in place ofNone.impossible(x)Prelude- Merged intoDebugandRuntimeRBTree- UseMapinsteadTrie- UseMapinsteadTrieMap- UseMaporpure/MapinsteadTrieSet- UseSetorpure/Setinstead
Data structure improvements
Section titled “Data structure improvements”The core package brings significant changes to data structures, making a clear separation between mutable and immutable (purely functional) APIs. All data structures can now be stored directly in stable memory.
| Data Structure | Description |
|---|---|
| List | Mutable list (originally mo:vector) |
| Map | Mutable map (originally mo:stableheapbtreemap) |
| Queue | Mutable queue |
| Set | Mutable set |
| Array | Immutable array |
| VarArray | Mutable array |
| pure/List | Immutable list (originally mo:base/List) |
| pure/Map | Immutable map (originally mo:base/OrderedMap) |
| pure/Set | Immutable set (originally mo:base/OrderedSet) |
| pure/Queue | Immutable queue (orginally mo:base/Deque) |
| pure/RealTimeQueue | Immutable queue with constant-time operations |
Interface changes by module
Section titled “Interface changes by module”Renamed functions
Section titled “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
Section titled “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
Parameter order changes
Section titled “Parameter order changes”indexOf(element, array, equal)→indexOf(array, equal, element)lastIndexOf(element, array, equal)→lastIndexOf(array, equal, element)nextIndexOf(element, array, fromInclusive, equal)→nextIndexOf(array, equal, element, fromInclusive)prevIndexOf(element, array, fromExclusive, equal)→prevIndexOf(array, equal, element, fromExclusive)
Removed functions
Section titled “Removed functions”take()- UsesliceToArray()insteadsortInPlace()- UseVarArray.sortInPlace()insteadtabulateVar()- UseVarArray.tabulate()instead
Modified functions
Section titled “Modified functions”fromArrayMut()→fromVarArray()hash()- Return type changed fromNat32toTypes.HashtoArrayMut()→toVarArray()
New functions
Section titled “New functions”empty()- Create an empty blob ("" : Blob)isEmpty()- Check if blob is emptysize()- Get number of bytes in a blob (equivalent toblob.size())
Renamed functions
Section titled “Renamed functions”logand()→logicalAnd()lognot()→logicalNot()logor()→logicalOr()logxor()→logicalXor()
New functions
Section titled “New functions”allValues()- Iterator over all boolean values
Renamed functions
Section titled “Renamed functions”isLowercase()→isLower()isUppercase()→isUpper()
Added functions
Section titled “Added functions”todo()- ReplacesPrelude.nyi()
Removed functions
Section titled “Removed functions”trap()- Moved toRuntime.trap()
Modified functions
Section titled “Modified functions”equal()- Now requires epsilon parameternotEqual()- Now requires epsilon parameter
Removed functions
Section titled “Removed functions”equalWithin(),notEqualWithin()- Useequal()andnotEqual()with epsilon
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.
New functions
Section titled “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)
Modified functions
Section titled “Modified functions”fromText()- Now returnsnullinstead of?0for the inputs ”+” and ”-“
Removed functions
Section titled “Removed functions”hash()hashAcc()
New functions
Section titled “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
Section titled “Int8, Int16, Int32, Int64, Nat8, Nat16, Nat32, Nat64”Renamed fields
Section titled “Renamed fields”maximumValue→maxValueminimumValue→minValue
New functions
Section titled “New functions”allValues()- Iterator over all values in rangerange(),rangeInclusive()- Range iterators (replacesIter.range())explode()- Slice into constituent bytes (only for sizes16,32,64)
Renamed functions
Section titled “Renamed functions”make()→some()- Create option from valueiterate()→forEach()- Apply function to option value
New functions
Section titled “New functions”compare()- Compare two optionstoText()- Convert option to text representation
Removed functions
Section titled “Removed functions”assertNull()- Removed in favor of pattern matchingassertSome()- Removed in favor of pattern matching
New functions
Section titled “New functions”allValues()- Iterator over all order values (#less,#equal,#greater)
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
Section titled “New classes”Random- Synchronous pseudo-random number generator for simulations and testingAsyncRandom- Asynchronous cryptographic random number generator using ICP entropy
Class methods
Section titled “Class methods”bool()- Random choice betweentrueandfalsenat8()- RandomNat8value in range[0, 256)nat64()- RandomNat64value in range[0, 2^64)nat64Range(from, to)- RandomNat64in range[from, to)natRange(from, to)- RandomNatin range[from, to)intRange(from, to)- RandomIntin range[from, to)
New functions
Section titled “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
New functions
Section titled “New functions”all()- Check all results in iteratorany()- Check any result satisfies predicateforOk()- Apply function to#okvalueforErr()- Apply function to#errvaluefromBool()- Create Result from boolean
Renamed functions
Section titled “Renamed functions”toLowercase()→toLower()toUppercase()→toUpper()translate()→flatMap()
New functions
Section titled “New functions”isEmpty()- Check if text is emptyreverse()- Swap the order of characterstoText()- Identity function
Removed functions
Section titled “Removed functions”hash()fromList()- UsefromIter()with list iterator insteadtoList()- UsetoIter()and convert to list if needed
Data structure migration examples
Section titled “Data structure migration examples”This section provides detailed migration examples showing how to convert common data structures from the base package to the core package. Each example demonstrates:
- Original implementation using the
basepackage with pre/post-upgrade hooks - Updated implementation using the
corepackage with automatic stable memory support - Migration pattern using the new
with migrationsyntax for seamless data structure conversion
Understanding the migration pattern
Section titled “Understanding the migration pattern”The with migration syntax follows this structure:
( with migration = func( state : { // Original state types } ) : { // New state types } = { // Conversion logic })persistent actorApp { // New stable declarations};It’s also possible to use a function defined in an imported module:
import { migrate } "Migration";
(with migration = migrate)persistent actorApp { // New stable declarations};This pattern ensures that existing stable data is preserved and converted to the new format during canister upgrades.
Buffer
Section titled “Buffer”Original (base)
Section titled “Original (base)”import Buffer "mo:base/Buffer";
persistent 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)
Section titled “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); })persistent actorApp { 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); };};Original (base)
Section titled “Original (base)”import Deque "mo:base/Deque";
persistent 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)
Section titled “Updated (core)”import Deque "mo:base/Deque"; // For migrationimport 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
Section titled “HashMap”Original (base)
Section titled “Original (base)”import HashMap "mo:base/HashMap";import Text "mo:base/Text";import Iter "mo:base/Iter";
persistent 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)
Section titled “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); })persistent 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
Section titled “OrderedMap”Original (base)
Section titled “Original (base)”import OrderedMap "mo:base/OrderedMap";import Text "mo:base/Text";import Iter "mo:base/Iter";
persistent 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)
Section titled “Updated (core)”import OrderedMap "mo:base/OrderedMap"; // For migrationimport 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 }; })persistent 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
Section titled “OrderedSet”Original (base)
Section titled “Original (base)”import OrderedSet "mo:base/OrderedSet";import Text "mo:base/Text";import Iter "mo:base/Iter";
persistent 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)
Section titled “Updated (core)”import OrderedSet "mo:base/OrderedSet"; // For migrationimport 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 }; })persistent actorApp { 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)); };};Original (base)
Section titled “Original (base)”import Trie "mo:base/Trie";import Text "mo:base/Text";import Iter "mo:base/Iter";
persistent 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)
Section titled “Updated (core)”import Trie "mo:base/Trie"; // For migrationimport 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); })persistent 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
Section titled “TrieMap”Original (base)
Section titled “Original (base)”import TrieMap "mo:base/TrieMap";import Text "mo:base/Text";import Iter "mo:base/Iter";
persistent 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)
Section titled “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); })persistent 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
Section titled “TrieSet”Original (base)
Section titled “Original (base)”import TrieSet "mo:base/TrieSet";import Text "mo:base/Text";
persistent 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)
Section titled “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); })persistent actorApp { 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)); };};Troubleshooting
Section titled “Troubleshooting”Version compatibility errors
Section titled “Version compatibility errors”If you encounter errors like field Array_tabulateVar does not exist in module, this indicates a version mismatch between your Motoko compiler and the core package.
Solution:
2. Ensure you’re using the latest Motoko compiler version
3. Update the core package to the latest version in your mops.toml
4. Clean and rebuild your project: dfx stop && dfx start --clean
Migration issues
Section titled “Migration issues”If you experience issues with the migration pattern:
- Ensure your project structure follows the new
with migrationsyntax exactly - Verify that all types referenced in the migration function are accessible (marked as
publicif needed) - Test the migration incrementally by converting one data structure at a time
For additional help, visit the ICP developer forum or Discord community.