Chapter 15. Modules
A Nim program can be split into different pieces called modules. Each module
needs to be in its own file and has its own scope. 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 are 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).
15.1. Export marker
If a declared symbol is marked with an asterisk () it is exported from the
current module:
proc exportedEcho(s: string) = echo s
proc *(a: string; b: int): string =
result = newStringOfCap(a.len * b)
for i in 1..b: result.add a
var exportedVar: int const exportedConst = 78 type
ExportedType* = object
exportedField*: int
15.2. Module processing The algorithm for compiling modules is: • Compile the whole module as usual, following import statements
recursively.
69
• If there is a cycle, only import the already parsed symbols (that are
exported); unknown identifiers cause a compile-time error.
This is best illustrated by an example: # Module A type
T1* = int # Module A exports the type `T1`
import B # the compiler starts parsing B proc main() =
var i = p(3) # works because B was parsed completely here
main() # Module B import A # A is not parsed here! Only the already known symbols
# of A are imported.
proc p*(x: A.T1): A.T1 =
# this works because T1 has already been
# added to A's interface symbol table
result = x + 1
15.3. 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 from being
imported:
import std/strutils except %, toUpperAscii
# doesn't work then:
echo "$1" % "abc".toUpperAscii
It is not checked that the except list is really exported from the module. This
feature allows us to compile against an older version of the module that does
not export these identifiers.
An import statement must be a top level statement, but it can be used within a
when statement:
70
when defined(posix):
import posix
else:
import windows
15.4. Include statement
The include statement inserts the contents of a Nim source code file at the
position where the include statement is placed. Note that include is
fundamentally different from import.
The include statement is useful to split up a large module into several files:
include fileA, fileB, fileC
The include statement can be used outside of the top level, as such:
# Module A
echo "Hello World!"
# Module B
proc main() =
include A
main() # => Hello World!
15.5. Module names in imports
A module alias can be introduced via the as keyword:
import std/strutils as su, std/sequtils as qu
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:
import lib/pure/os, "lib/pure/times"
71
Note that the module name is still strutils and not lib/pure/strutils and so one cannot do: import lib/pure/strutils echo lib/pure/strutils.toUpperAscii("abc") Likewise, the following is invalid as the name is strutils already: import lib/pure/strutils as strutils 15.6. 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: import "gfx/3d/somemodule" # in quotes because '3d' is not a valid Nim identifier 15.7. 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:
pkg: The pkg pseudo directory is used to unambiguously refer to a Nimble
package. However, for technical details that lie outside 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.
72
15.8. 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:
from std/strutils import %
echo "$1" % "abc"
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. 15.9. Export statement An export statement can be used for symbol forwarding so that client modules don’t need to import a module’s dependencies:
type MyObject* = object
import B
export B.MyObject
proc $*(x: MyObject): string = "my object"
import A
var x: MyObject 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:
73
import foo/bar/baz export baz 15.10. Scope rules Identifiers are valid from the point of their declaration until the end of the block in which the declaration occurred. The range where the identifier is known is the scope of the identifier. The exact scope of an identifier depends on the way it was declared. 15.10.1. Block scope The scope of a variable declared in the declaration part of a block is valid from the point of declaration until the end of the block. If a block contains a second block, in which the identifier is re-declared, then inside this block, the second declaration will be valid. Upon leaving the inner block, the first declaration is valid again. An identifier cannot be redefined in the same block, except if valid for overloading purposes. 15.10.2. Tuple or object scope The field identifiers inside a tuple or object definition are valid in the following places: • To the end of the tuple/object definition. • Field designators of a variable of the given tuple/object type. • In all descendant types of the object type. 15.10.3. Module scope All identifiers of a module are valid from the point of declaration until the end of the module. Identifiers from indirectly dependent modules are not available. The system module is automatically imported in every module. If a module imports an identifier by two different modules, each occurrence of the identifier has to be qualified unless it is an overloaded procedure or iterator in which case the overloading resolution takes place: 74
var x*: string
var x*: int
import A, B write(stdout, x) # error: x is ambiguous write(stdout, A.x) # no error: qualifier used var x = 4 write(stdout, x) # not ambiguous: uses the module C's x