Modules and imports

This section provides examples of different scenarios for using the module and import keywords.

To illustrate how these keywords are used, let’s step through some sample code.

Importing from the Motoko base library

One of the most common import scenarios is one that you see illustrated in the examples in this guide, in the Motoko projects in the examples repository, and in the tutorials involves importing modules from the Motoko base library. Importing modules from the base library enables you to re-use the values, functions and types defined in those modules rather than writing similar ones from scratch.

The following two lines import functions from the Array and Result modules:

import Array "mo:base/Array";
import Result "mo:base/Result";

Notice that the import declaration includes the mo: prefix to identify the module as a Motoko module and that the declaration does not include the .mo file type extension.

Importing local files

Another common approach to writing programs in Motoko involves splitting up the source code into different modules. For example, you might design an application to use the following model:

  • a main.mo file to contain the actor and functions that change state.

  • a types.mo file for all of your custom type definitions.

  • a utils.mo file for functions that do work outside of the actor.

In 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";

Because these lines import modules from the local project instead of the Motoko library, these import declarations 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. Once again, import does not use the `.mo` file suffix.

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 the Vessel 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 from another canister

In addition to the examples above that import Motoko modules, you can also import functions from canisters by using the canister: prefix in place of the mo: prefix.

For example, you might have a project that produces the following three canisters:

  • BigMap

  • Connectd

  • LinkedUp

These three canisters are declared in the project’s dfx.json configuration file and compiled by running dfx build.

You can then use the following lines to import the functions from the BigMap and Connectd canisters in the LinkedUp program:

import BigMap "canister:BigMap";
import Connectd "canister:connectd";

When importing canisters, it is important to note that the type for the imported canister corresponds to a Motoko actor instead of a Motoko module. This distinction can affect how some data structures are typed.

For the imported canister actor, types are derived from the Candid file—the project-name.did file—for the canister rather than from Motoko itself.

The translation from Motoko actor type to Candid service type is mostly, but not entirely, one-to-one, and there are some distinct Motoko types that map to the same Candid type. For example, the Motoko Nat8 and Word8 types both exported as Candid type nat8, but nat8 is canonically imported as Motoko Nat8, not Word8.

The type of an imported canister function, therefore, might differ from the type of the original Motoko code that implements it. For example, if the Motoko function had type +shared Word8 → async Word64 in the implementation, its exported Candid type would be (Nat8) -> Nat64 but the Motoko type imported from this Candid type will actually be the correct—but perhaps unexpected—type shared Nat8 -> async Nat64.

These type differences are the result of the Candid-to-Motoko composition layer inherent to the canister abstraction.

Naming imported modules

Although the most common convention is to identify imported modules by the module name as illustrated in the examples above, there’s no requirement for you to do so. For example, you might want to use different names to avoid naming conflicts or to simplify the naming scheme.

The following examples illustrate different names you might use when importing the List base library module, avoiding a clash with another List library from a fictional collections package.

import List  "mo:base/List:";
import Sequence  "mo:collections/List";
import L "mo:base/List";