Modules and imports
Motoko minimizes built-in types and operations, relying on a core package of modules to provide essential functionality. This modular approach keeps the language simple.
The examples in this section show how to use the module and import keywords in different scenarios.
Importing from the core package
Section titled “Importing from the core package”The Motoko core package includes common utilities for working with data structures, debugging, and other functionality. To import from the core package, use the import keyword, followed by the mo:core/<name> module path.
import Debug "mo:core/Debug";
Debug.print("Hello, world!");The mo: prefix identifies a Motoko module. The declaration does not include the .mo file extension.
You can also selectively import and rename a subset of named values and types from a module by using the object pattern syntax:
import { type List; get; foldLeft = fold } "mo:core/List";Importing from another file
Section titled “Importing from another file”Projects may split code into multiple files for better organization, such as:
src/project_backend ├── main.mo // Contains the main actor ├── types.mo // Stores type definitions ├── utils.mo // Contains helper functionsIn this scenario, you might place all three files in the same directory and use a local import to make the functions available where they are needed.
For example, the main.mo contains the following lines to reference the modules in the same directory:
import Types "types";import Utils "utils";These lines import modules from the local project instead of the Motoko library and don’t use the mo: prefix. In this example, both the types.mo and utils.mo files are in the same directory as the main.mo file.
Importing from another package or directory
Section titled “Importing from another package or directory”You can also import modules from other packages or from directories other than the local directory.
For example, the following lines import modules from a redraw package that is defined as a dependency:
import Render "mo:redraw/Render";import Mono5x5 "mo:redraw/glyph/Mono5x5";You can define dependencies for a project using a package manager or in the project dfx.json configuration file.
In this example, the Render module is in the default location for source code in the redraw package and the Mono5x5 module is in a redraw package subdirectory called glyph.
Importing packages from a package manager
Section titled “Importing packages from a package manager”Modules can also be imported from other packages, such as those imported from a package manager.
Dependencies are managed using a package manager or defined in the project’s dfx.json file. Motoko supports package managers like Mops to install third-party libraries.
Configuring the package manager in dfx.json
Section titled “Configuring the package manager in dfx.json”{ "defaults": { "build": { "packtool": "mops sources" } }}Installing a package with a package manager
Section titled “Installing a package with a package manager”With Mops, add the package to your project’s dependencies with mops add.
Then import the package into your Motoko code:
import Vec "mo:vector";Naming imported modules
Section titled “Naming imported modules”While the imported module name usually matches the file name, custom names can be used to avoid conflicts or simplify references.
import List "mo:core/List";import L "mo:core/List";import PureList "mo:core/pure/List";Importing from another canister
Section titled “Importing from another canister”Actors and their functions can be imported from other canisters using the canister: prefix.
import BigMap "canister:BigMap";import Connectd "canister:Connectd";BigMap and Connectd are separate canisters defined in dfx.json. Canister functions are shared and may require await to call them.
Unlike a Motoko module, an imported canister:
- Can be implemented in any language that emits a Candid interface.
- Has its type derived from a
.didfile, not from Motoko itself.
Importing actor classes
Section titled “Importing actor classes”When imported, an actor class provides a type definition describing the class interface and a function that returns an instance of the class.
For example, if you define the following actor class:
persistent actor class Counter(init : Nat) { var count = init;
public func inc() : async () { count += 1 };
public func read() : async Nat { count };
public func bump() : async Nat { count += 1; count; };};It can be imported into another file:
import Counters "Counters";import Debug "mo:core/Debug";import Nat "mo:core/Nat";
persistent actor CountToTen { public func countToTen() : async () { let counterActor = await Counters.Counter(1); while ((await counterActor.read()) < 10) { Debug.print(Nat.toText(await counterActor.read())); await counterActor.inc(); }; }};Counters.Counter(1) installs a new counter on the network. Installation is asynchronous, so the result is awaited. If the actor class is not named, it will result in a bad import error because actor class imports cannot be anonymous.
Importing Blob values
Section titled “Importing Blob values”The import syntax can also be used with the blob:file: URI scheme to import raw Blob values:
import pub = "blob:file:./keys/id_ed25519.pub";
actor { func checkSig(key : Blob, cyphertext : Text) { ... };
public func verify(cyphertext : Text) : async () { checkSig(pub, cyphertext); };};This use case also caters for the import of externally-built Wasm Blobs for low-level installation and upgrade by means of the management canister.