Module for working with Blobs (immutable sequences of bytes).
Blobs represent sequences of bytes. They are immutable, iterable, but not indexable and can be empty.
Byte sequences are also often represented as [Nat8]
, i.e. an array of bytes, but this representation is currently much less compact than Blob
, taking 4 physical bytes to represent each logical byte in the sequence.
If you would like to manipulate Blobs, it is recommended that you convert
Blobs to [var Nat8]
or Buffer<Nat8>
, do the manipulation, then convert back.
Import from the base library to use this module.
motoko name=import
import Blob "mo:base/Blob";
Some built in features not listed in this module:
Blob
literal from a Text
literal, provided the context expects an expression of type Blob
.b.size() : Nat
returns the number of bytes in the blob b
;b.values() : Iter.Iter<Nat8>
returns an iterator to enumerate the bytes of the blob b
.For example:
motoko include=import
import Debug "mo:base/Debug";
import Nat8 "mo:base/Nat8";
let blob = "\00\00\00\ff" : Blob; // blob literals, where each byte is delimited by a back-slash and represented in hex
let blob2 = "charsγγγ" : Blob; // you can also use characters in the literals
let numBytes = blob.size(); // => 4 (returns the number of bytes in the Blob)
for (byte : Nat8 in blob.values()) { // iterator over the Blob
Debug.print(Nat8.toText(byte))
}
Boolean type and operations.
While boolean operators _ and _
and _ or _
are short-circuiting,
avoiding computation of the right argument when possible, the functions
logand(_, _)
and logor(_, _)
are strict and will always evaluate both
of their arguments.
Certified data.
The Internet Computer allows canister smart contracts to store a small amount of data during update method processing so that during query call processing, the canister can obtain a certificate about that data.
This module provides a low-level interface to this API, aimed at advanced users and library implementors. See the Internet Computer Functional Specification and corresponding documentation for how to use this to make query calls to your canister tamperproof.
Utilities for Char
(character)
Managing cycles within actors in the Internet Computer Protocol (ICP).
The usage of the Internet Computer is measured, and paid for, in cycles. This library provides imperative operations for observing cycles, transferring cycles, and observing refunds of cycles.
NOTE: Since cycles measure computational resources, the value of balance()
can change from one call to the next.
Example for use on the ICP:
motoko no-repl
import Cycles "mo:base/Cycles";
import Debug "mo:base/Debug";
actor {
public func main() : async() {
Debug.print("Main balance: " # debug_show(Cycles.balance()));
Cycles.add<system>(15_000_000);
await operation(); // accepts 10_000_000 cycles
Debug.print("Main refunded: " # debug_show(Cycles.refunded())); // 5_000_000
Debug.print("Main balance: " # debug_show(Cycles.balance())); // decreased by around 10_000_000
};
func operation() : async() {
Debug.print("Operation balance: " # debug_show(Cycles.balance()));
Debug.print("Operation available: " # debug_show(Cycles.available()));
let obtained = Cycles.accept<system>(10_000_000);
Debug.print("Operation obtained: " # debug_show(obtained)); // => 10_000_000
Debug.print("Operation balance: " # debug_show(Cycles.balance())); // increased by 10_000_000
Debug.print("Operation available: " # debug_show(Cycles.available())); // decreased by 10_000_000
}
}
Utility functions for debugging.
Import from the base library to use this module.
motoko name=import
import Debug "mo:base/Debug";
Error values and inspection.
The Error
type is the argument to throw
, parameter of catch
.
The Error
type is opaque.
Double precision (64-bit) floating-point numbers in IEEE 754 representation.
This module contains common floating-point constants and utility functions.
Notation for special values in the documentation below:
+inf
: Positive infinity
-inf
: Negative infinity
NaN
: "not a number" (can have different sign bit values, but NaN != NaN
regardless of the sign).
Note: Floating point numbers have limited precision and operations may inherently result in numerical errors.
Examples of numerical errors:
0.1 + 0.1 + 0.1 == 0.3 // => false
1e16 + 1.0 != 1e16 // => false
(and many more cases)
Advice:
==
or !=
are discouraged. Instead, it is better to compare
floating-point numbers with a numerical tolerance, called epsilon.Example:
import Float "mo:base/Float";
let x = 0.1 + 0.1 + 0.1;
let y = 0.3;
let epsilon = 1e-6; // This depends on the application case (needs a numerical error analysis).
Float.equal(x, y, epsilon) // => true
NaN sign:
abs
, neg
, and copySign
. Other operations can have an arbitrary
sign bit for NaN results.Functions on functions, creating functions from simpler inputs.
(Most commonly used when programming in functional style using higher-order functions.)
Hash values
Signed integer numbers with infinite precision (also called big integers).
Most operations on integer numbers (e.g. addition) are available as built-in operators (e.g. -1 + 1
).
This module provides equivalent functions and Text
conversion.
Import from the base library to use this module.
motoko name=import
import Int "mo:base/Int";
Utility functions on 16-bit signed integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Int16 "mo:base/Int16";
Utility functions on 32-bit signed integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Int32 "mo:base/Int32";
Utility functions on 64-bit signed integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Int64 "mo:base/Int64";
Utility functions on 8-bit signed integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Int8 "mo:base/Int8";
Low-level interface to the Internet Computer.
Utilities for Iter
(iterator) values
Original: vector
Mops package?
An imperative key-value map based on order/comparison of the keys. The map data structure type is stable and can be used for orthogonal persistence.
Example:
import Map "mo:base/Map";
import Nat "mo:base/Nat";
persistent actor {
let numberNames = Map.empty<Nat, Text>();
Map.add(numberNames, Nat.compare, 0, "Zero");
Map.add(numberNames, Nat.compare, 1, "One");
Map.add(numberNames, Nat.compare, 2, "Two");
}
The internal implementation is a B-tree with order 32.
Performance:
O(log(n))
worst case cost per insertion, removal, and retrieval operation.O(n)
for storing the entire tree.
n
denotes the number of key-value entries stored in the map.Natural numbers with infinite precision.
Most operations on natural numbers (e.g. addition) are available as built-in operators (e.g. 1 + 1
).
This module provides equivalent functions and Text
conversion.
Import from the base library to use this module.
motoko name=import
import Nat "mo:base/Nat";
Utility functions on 16-bit unsigned integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Nat16 "mo:base/Nat16";
Utility functions on 32-bit unsigned integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Nat32 "mo:base/Nat32";
Utility functions on 64-bit unsigned integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Nat64 "mo:base/Nat64";
Utility functions on 8-bit unsigned integers.
Note that most operations are available as built-in operators (e.g. 1 + 1
).
Import from the base library to use this module.
motoko name=import
import Nat8 "mo:base/Nat8";
Typesafe nullable values.
Optional values can be seen as a typesafe null
. A value of type ?Int
can
be constructed with either null
or ?42
. The simplest way to get at the
contents of an optional is to use pattern matching:
let optionalInt1 : ?Int = ?42;
let optionalInt2 : ?Int = null;
let int1orZero : Int = switch optionalInt1 {
case null 0;
case (?int) int;
};
assert int1orZero == 42;
let int2orZero : Int = switch optionalInt2 {
case null 0;
case (?int) int;
};
assert int2orZero == 0;
The functions in this module capture some common operations when working with optionals that can be more succinct than using pattern matching.
Utilities for Order
(comparison between two values).
Module for interacting with Principals (users and canisters).
Principals are used to identify entities that can interact with the Internet Computer. These entities are either users or canisters.
Example textual representation of Principals:
un4fu-tqaaa-aaaab-qadjq-cai
In Motoko, there is a primitive Principal type called Principal
. As an example
of where you might see Principals, you can access the Principal of the
caller of your shared function.
motoko no-repl
shared(msg) func foo() {
let caller : Principal = msg.caller;
};
Then, you can use this module to work with the Principal
.
Import from the base library to use this module.
motoko name=import
import Principal "mo:base/Principal";
Double-ended queue of a generic element type T
.
The interface is imperative, not purely functional. In particular, Queue operations such as push and pop update their input queue instead of returning the value of the modified Queue.
Examples of use-cases:
Queue (FIFO) by using pushBack()
and popFront()
.
Stack (LIFO) by using pushFront()
and popFront()
.
A Queue is internally implemented as two lists, a head access list and a (reversed) tail access list, that are dynamically size-balanced by splitting.
Construction: Create a new queue with the empty<T>()
function.
Note on the costs of push and pop functions:
O(1)
amortized costs, O(n)
worst case cost per single call.O(1)
amortized costs, O(n)
worst case cost per single call.n
denotes the number of elements stored in the queue.
Random number generation.
Byte-level access to isolated, virtual stable memory regions.
This is a moderately lightweight abstraction over IC stable memory and supports persisting
regions of binary data across Motoko upgrades.
Use of this module is fully compatible with Motoko's use of
stable variables, whose persistence mechanism also uses (real) IC stable memory internally, but does not interfere with this API.
It is also fully compatible with existing uses of the ExperimentalStableMemory
library, which has a similar interface, but,
only supported a single memory region, without isolation between different applications.
The Region
type is stable and can be used in stable data structures.
A new, empty Region
is allocated using function new()
.
Regions are stateful objects and can be distinguished by the numeric identifier returned by function id(region)
.
Every region owns an initially empty, but growable sequence of virtual IC stable memory pages.
The current size, in pages, of a region is returned by function size(region)
.
The size of a region determines the range, [ 0, ..., size(region)*2^16 ), of valid byte-offsets into the region; these offsets are used as the source and destination of load
/store
operations on the region.
Memory is allocated to a region, using function grow(region, pages)
, sequentially and on demand, in units of 64KiB logical pages, starting with 0 allocated pages.
A call to grow
may succeed, returning the previous size of the region, or fail, returning a sentinel value. New pages are zero initialized.
A size of a region can only grow and never shrink. In addition, the stable memory pages allocated to a region will not be reclaimed by garbage collection, even if the region object itself becomes unreachable.
Growth is capped by a soft limit on physical page count controlled by compile-time flag
--max-stable-pages <n>
(the default is 65536, or 4GiB).
Each load
operation loads from region relative byte address offset
in little-endian
format using the natural bit-width of the type in question.
The operation traps if attempting to read beyond the current region size.
Each store
operation stores to region relative byte address offset
in little-endian format using the natural bit-width of the type in question.
The operation traps if attempting to write beyond the current region size.
Text values can be handled by using Text.decodeUtf8
and Text.encodeUtf8
, in conjunction with loadBlob
and storeBlob
.
The current region allocation and region contents are preserved across upgrades.
NB: The IC's actual stable memory size (ic0.stable_size
) may exceed the
total page size reported by summing all regions sizes.
This (and the cap on growth) are to accommodate Motoko's stable variables and bookkeeping for regions.
Applications that plan to use Motoko stable variables sparingly or not at all can
increase --max-stable-pages
as desired, approaching the IC maximum (initially 8GiB, then 32Gib, currently 64Gib).
All applications should reserve at least one page for stable variable data, even when no stable variables are used.
Usage:
motoko no-repl
import Region "mo:base/Region";
Error handling with the Result type.
Original: OrderedSet.mo
Mutable stack data structure.
Utility functions for Text
values.
A Text
value represents human-readable text as a sequence of characters of type Char
.
let text = "Hello!";
let size = text.size(); // 6
let iter = text.chars(); // iterator ('H', 'e', 'l', 'l', 'o', '!')
let concat = text # " π"; // "Hello! π"
The "mo:base/Text"
module defines additional operations on Text
values.
Import the module from the base library:
motoko name=import
import Text "mo:base/Text";
Note: Text
values are represented as ropes of UTF-8 character sequences with O(1) concatenation.
System time utilities and timers.
The following example illustrates using the system time:
import Int = "mo:base/Int";
import Time = "mo:base/Time";
actor {
var lastTime = Time.now();
public func greet(name : Text) : async Text {
let now = Time.now();
let elapsedSeconds = (now - lastTime) / 1000_000_000;
lastTime := now;
return "Hello, " # name # "!" #
" I was last called " # Int.toText(elapsedSeconds) # " seconds ago";
};
};
Note: If moc
is invoked with -no-timer
, the importing will fail.
Note: The resolution of the timers is in the order of the block rate,
so durations should be chosen well above that. For frequent
canister wake-ups the heartbeat mechanism should be considered.
Timers for one-off or periodic tasks. Applicable as part of the default mechanism.
If moc
is invoked with -no-timer
, the importing will fail. Furthermore, if passed --trap-on-call-error
, a congested canister send queue may prevent timer expirations to execute at runtime. It may also deactivate the global timer.
The resolution of the timers is similar to the block rate, so durations should be chosen well above that. For frequent canister wake-ups, consider using the heartbeat mechanism; however, when possible, canisters should prefer timers.
The functionality described below is enabled only when the actor does not override it by declaring an explicit system func timer
.
Timers are not persisted across upgrades. One possible strategy
to re-establish timers after an upgrade is to use stable variables
in the post_upgrade
hook and distill necessary timer information
from there.
Using timers for security (e.g., access control) is strongly discouraged. Make sure to inform yourself about state-of-the-art dapp security. If you must use timers for security controls, be sure to consider reentrancy issues as well as the vanishing of timers on upgrades and reinstalls.
For further usage information for timers on the IC, please consult the documentation.
Mutable array utilities.
Original: OrderedMap.mo
Double-ended queue of a generic element type T
.
The interface is purely functional, not imperative, and queues are immutable values. In particular, Queue operations such as push and pop do not update their input queue but, instead, return the value of the modified Queue, alongside any other data. The input queue is left unchanged.
Examples of use-cases:
Queue (FIFO) by using pushBack()
and popFront()
.
Stack (LIFO) by using pushFront()
and popFront()
.
A Queue is internally implemented as two lists, a head access list and a (reversed) tail access list, that are dynamically size-balanced by splitting.
Construction: Create a new queue with the empty<T>()
function.
Note on the costs of push and pop functions:
O(1)
amortized costs, O(n)
worst case cost per single call.O(1)
amortized costs, O(n)
worst case cost per single call.n
denotes the number of elements stored in the queue.
Original: OrderedSet.mo
Immutable singly-linked list
The Iterator type
Provides extended utility functions on Arrays.
Note the difference between mutable and non-mutable arrays below.
WARNING: If you are looking for a list that can grow and shrink in size, it is recommended you use either the Buffer class or the List class for those purposes. Arrays must be created with a fixed size.
Import from the base library to use this module.