Modules
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
{.interpreted-text
role="idx"} statement. Recursive module dependencies
{.interpreted-text
role="idx"} 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
).
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:
# 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 has been 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 the compiler has already
# added T1 to A's interface symbol table
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
from being imported:
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.
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:
The include
statement can be used outside of the top level, as such:
Module names in imports
A module alias can be introduced via the as
keyword:
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:
Note that the module name is still strutils
and not
lib/pure/strutils
and so one cannot do:
Likewise, the following does not make sense as the name is strutils
already:
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:
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:
- `std`: The
std
pseudo directory is the abstract location of Nim\'s standard library. For example, the syntaximport std / strutils
is used to unambiguously refer to the standard library\'sstrutils
module. - `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 ofstd
.
It is recommended and preferred but not currently enforced that all stdlib module imports include the std/ \"pseudo directory\" as part of the import name.
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"
# always possible: full qualification:
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:
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:
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.
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 redeclared, 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 procedure or iterator overloading purposes.
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.
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:
# Module C
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
Packages
A collection of modules in a file tree with an identifier.nimble
file
in the root of the tree is called a Nimble package. A valid package name
can only be a valid Nim identifier and thus its filename is
identifier.nimble
where identifier
is the desired package name. A
module without a .nimble
file is assigned the package identifier:
unknown
.
The distinction between packages allows diagnostic compiler messages to be scoped to the current project\'s package vs foreign packages.