Modules

Nim supports splitting a program into pieces by a module concept. Each module needs to be in its own file and has its own namespace. Modules enable information hiding and separate compilation. A module may gain access to symbols of another module by the import statement. Recursive module dependencies are allowed, but slightly subtle. Only top-level symbols that are marked with an asterisk (*) are exported. A valid module name can only be a valid Nim identifier (and thus its filename is identifier.nim).

The algorithm for compiling modules is:

  • compile the whole module as usual, following import statements recursively
  • if there is a cycle only import the already parsed symbols (that are exported); if an unknown identifier occurs then abort

This is best illustrated by an example:

  1. # Module A
  2. type
  3. T1* = int # Module A exports the type ``T1``
  4. import B # the compiler starts parsing B
  5. proc main() =
  6. var i = p(3) # works because B has been parsed completely here
  7. main()
  1. # Module B
  2. import A # A is not parsed here! Only the already known symbols
  3. # of A are imported.
  4. proc p*(x: A.T1): A.T1 =
  5. # this works because the compiler has already
  6. # added T1 to A's interface symbol table
  7. result = x + 1

Import statement

After the import statement a list of module names can follow or a single module name followed by an except list to prevent some symbols to be imported:

  1. import strutils except `%`, toUpperAscii
  2. # doesn't work then:
  3. echo "$1" % "abc".toUpperAscii

It is not checked that the except list is really exported from the module. This feature allows to compile against an older version of the module that does not export these identifiers.

The import statement is only allowed at the top level.

Include statement

The include statement does something fundamentally different than importing a module: it merely includes the contents of a file. The include statement is useful to split up a large module into several files:

  1. include fileA, fileB, fileC

The include statement can be used outside of the top level, as such:

  1. # Module A
  2. echo "Hello World!"
  1. # Module B
  2. proc main() =
  3. include A
  4. main() # => Hello World!

Module names in imports

A module alias can be introduced via the as keyword:

  1. import strutils as su, sequtils as qu
  2. echo su.format("$1", "lalelu")

The original module name is then not accessible. The notations path/to/module or “path/to/module” can be used to refer to a module in subdirectories:

  1. import lib/pure/os, "lib/pure/times"

Note that the module name is still strutils and not lib/pure/strutils and so one cannot do:

  1. import lib/pure/strutils
  2. echo lib/pure/strutils.toUpperAscii("abc")

Likewise the following does not make sense as the name is strutils already:

  1. import lib/pure/strutils as strutils

Collective imports from a directory

The syntax import dir / [moduleA, moduleB] can be used to import multiple modules from the same directory.

Path names are syntactically either Nim identifiers or string literals. If the path name is not a valid Nim identifier it needs to be a string literal:

  1. import "gfx/3d/somemodule" # in quotes because '3d' is not a valid Nim identifier

Pseudo import/include paths

A directory can also be a so called “pseudo directory”. They can be used to avoid ambiguity when there are multiple modules with the same path.

There are two pseudo directories:

1. std: The std pseudo directory is the abstract location of Nim’s standard library. For example, the syntax import std / strutils is used to unambiguously refer to the standard library’s strutils module.

2. pkg: The pkg pseudo directory is used to unambiguously refer to a Nimble package. However, for technical details that lie outside of the scope of this document its semantics are: Use the search path to look for module name but ignore the standard library locations. In other words, it is the opposite of std.

From import statement

After the from statement a module name follows followed by an import to list the symbols one likes to use without explicit full qualification:

  1. from strutils import `%`
  2. echo "$1" % "abc"
  3. # always possible: full qualification:
  4. echo strutils.replace("abc", "a", "z")

It’s also possible to use from module import nil if one wants to import the module but wants to enforce fully qualified access to every symbol in module.

Export statement

An export statement can be used for symbol forwarding so that client modules don’t need to import a module’s dependencies:

  1. # module B
  2. type MyObject* = object
  1. # module A
  2. import B
  3. export B.MyObject
  4. proc `$`*(x: MyObject): string = "my object"
  1. # module C
  2. import A
  3. # B.MyObject has been imported implicitly here:
  4. var x: MyObject
  5. echo $x

When the exported symbol is another module, all of its definitions will be forwarded. One can use an except list to exclude some of the symbols.

Notice that when exporting, one needs to specify only the module name:

  1. import foo/bar/baz
  2. export baz