ch15.md 7.5 KB

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:

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

    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. 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:

    module B

    type MyObject* = object

    module A

    import B export B.MyObject proc $*(x: MyObject): string = "my object"

    module C

    import A

    B.MyObject was imported implicitly here:

    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

    Module A

    var x*: string

    Module B

    var x*: int

    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