Skip to content
LogoLogo

Imports

import names another file, a module, and brings its public declarations into this file. Imports come first in a file, before any other declaration. A path that starts with std/ names a module of the standard library, built into the compiler. Any other path is relative to the importing file.

import "./lib/math";
import "./lib/fees" as Fees;
 
storage is record {
  fee : Fees.BasisPoints;
  cap : uint;
}
 
external function configure(const fee : Fees.BasisPoints; const cap : uint) is {
  storage.fee := fee;
  storage.cap := cap;
}
 
external view function charge(const amount : uint) : uint is
  min(Fees.apply(storage.fee, amount), storage.cap)

Import paths

An import path names a module, not a file: import "std/evm";, import "./lib/fees";, import "../shared/errors" as E;. The compiler adds .lithe, so "./lib/fees" is lib/fees.lithe next to the importing file.

A path is std/, ./, or one or more ../, then names separated by /. Each name is a lowercase identifier: letters a to z, digits, and _, not starting with a digit. Anything else is an invalid import path, such as "./fees.lithe", "fees", or "./my-lib":

import "./fees.lithe";
 
storage is record {
  n : uint;
}
invalid import path `./fees.lithe`

A path names one file, never a directory, so std/crypto and std/crypto/ecdsa are two modules that one program can import side by side.

What a file sees

A file's names are exactly:

  • the language's own: types, literals, conversions such as uint(x), Some and None, and assert, fail, and forward;
  • its own declarations;
  • the public declarations of each file it imports, behind the alias when the import has one.

Nothing else. A name reaches a file only through that file's own imports, never through another file's. If main.lithe imports ./a, and a imports ./b, then main can't name anything b declares until it imports ./b itself:

unknown type `Amount`

`Amount` is declared in `./b`: `import "./b";`

Naming is what needs the import. A value can come from anywhere: main can call a's function that returns b's Amount, and pass the result along, without importing b.

So a file's imports list everything it depends on. A file that doesn't import std/evm doesn't read the caller, the block, or balances, and a reviewer can see that from its first lines. lithe lint reports an import the file doesn't use, and the language server adds the import a name needs.

Code resolves where it's written

Code always sees the names of the file that declares it, wherever it runs. A module's body, the code a contract pulls in with use, and each instance of a generic module such as Set(address) resolve their names in their own file. The importing file's names never reach into them, and their file's imports never reach out: a contract can declare its own Denied error while a module it uses raises the Denied its own file imports.

One file, however it is imported

A file is loaded once, however many files import it and under whichever aliases. If a imports ./b as M, c imports it plainly, and main imports it as B, then M.addOne in a, addOne in c, and B.addOne in main are one function. b's modules have one storage, and its errors and events have one selector.

Plain and aliased imports

A plain import, import "./lib/math";, makes the file's names available directly, as min. Two plain imports that define the same name are an error, whether or not the name is used. The error says where each is, and how to resolve it:

main.lithe:2:1: ambiguous symbol `calculateFee`

defined in:
- left.lithe:1:6
- right.lithe:1:6

rename one, or import `./right` with an alias: `import "./right" as Name;`

Declaring one name twice in a file is an error too, and it points at the first declaration:

main.lithe:8:6: duplicate function `scale`

`scale` is already declared at 5:6 as a pure function

overloads need different parameter types; rename one or change its parameters

An aliased import, import "./lib/fees" as Fees;, keeps the file's names behind the alias: Fees.apply, Fees.BasisPoints. That includes its modules: import "std/math/math" as M; names the module M.Math, so a call is M.Math.sqrt(x) and a contract writes use M.Math;. An alias doesn't shorten a module's name; it keeps two files' names apart, so two files that each declare a module Tool can both be imported, as P.Tool and Q.Tool. Aliases avoid clashes, and they make it obvious at each use where a name comes from.

Built-in modules

Evm, Crypto, Abi, Bytes, String, Base64, List, Uint, Int, and Address are modules of the standard library, each in its own file, and a file imports the ones it uses like any other module: import "std/evm"; for Evm.sender(), import "std/uint"; for Uint.max. An alias works the same way: after import "std/evm" as Chain;, the caller is Chain.Evm.sender(). See Built-ins.

A file that doesn't import std/evm is free to declare its own module Evm. In a file that does, a second Evm is a duplicate module.

What a file can export

An imported file can declare types, errors, events, internal functions, interfaces, and modules. Its private declarations stay inside it:

unknown function `hidden`

A file of contracts can be imported too, without an alias. Its contracts don't join this file's, but this file's contracts can defer create them. Nothing else in the file comes with them.

Entrypoints belong to the contract, not to libraries. An imported file cannot declare external functions of its own, use a module exposing its externals, or declare supports: a library offers externals from its modules, and the contract declares the ones it exposes and announces. A library file should not declare storage. Put state in a module with named storage, and have the contract use it.

Importing modules

The usual way to share code is a module in its own file:

import "./lib/ledger";
 
contract Token is {
  use Ledger exposing {
    function mint(to : address; amount : uint);
    view function balanceOf(who : address) : uint;
  }
}

Rules

  • Imports must come before every other declaration.
  • An import path names a module: std/, ./, or ../, and lowercase names, without .lithe.
  • A file sees its own declarations and those of the files it imports, and nothing else.
  • Import cycles are rejected: cyclic module dependency detected.
  • Every file is loaded once, however many files import it.