Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Modules and Imports

Modules are the largest block of organization in MiniD. They are similar to D modules in purpose and use, but are much simpler.

Usually one uses modules to organize large portions of code. For example, if you were writing a game, you might have several modules such as "ai", "camera", "items", "sound", etc. with each module defining functions and classes pertinent to those areas of the game scripting.

Modules usually have a one-to-one correspondence with files. A single file holds a single module. Module names can also be hierarchical, a relationship that maps well to common file systems.

Modules

Module:
	ModuleDeclaration {Statement} EOF

ModuleDeclaration:
	'module' Identifier {'.' Identifier} ';'

Modules declare a namespace into which all global declarations in the module are inserted. If you think of the global namespace in MiniD as a hierarchy, you have the root namespace in which lives the base library and any top-level namespaces. Each namespace can then have namespaces nested inside. So a module named "a.b" will be a namespace named "b" inside another namespace named "a", which is itself in the global namespace.

All modules must begin with a module declaration. This declares the name of the module, and if it has more than one part, where the module lives in the global hierarchy.

module a.b;

MiniD is more strict than D with the naming of modules. If you name a module "a" but the source file is "b.md", when you perform an "import b" from another module, the interpreter will give you an error telling you that the filename and the module declaration names don't match. The naming is also case-sensitive.

When you have any global declaration in a module, that global is inserted into the module namespace for other modules to see. Local declarations are visible only within the module. Thus local and global parallel public and private in D.

module a;

// This will be seen as "a.x" from other modules.
global x = 5;

// This is not visible to other modules.
local y = 10;

Imports

ImportStatement:
	'import' Identifier {'.' Identifier} [':' Identifier {',' Identifier}] ';'
	'import' '(' Expression ')' [':' Identifier {',' Identifier}] ';'

When you need access to symbols from other modules, you use an import statement. Import statements can appear anywhere any statement may appear, and are executed just as other statements. This means you can import modules conditionally. There are two forms of import statements; see Statements for info on that.

Importing modules does not perform a symbolic import as in D. Import statements are more like a declaration of which modules this module depends upon. Since modules are loaded only once into the global namespace, after they have been imported at least once, any code can actually see the module without importing it. However, it's much better to always explicitly import modules which the current module depends upon. This will avoid any problems if you were to change the order of module loading or if you change the names of modules. Always explicitly importing all necessary modules also makes it easy to keep track of dependencies between your modules.

When a module is imported, it goes through a process, as follows.

  1. The interpreter checks to see if the module has already been loaded. If it has, it stops here successfully.
  2. The interpreter checks to see if the module is being circularly imported. Circular imports are not allowed, and will cause an error at this step.
  3. If the module hasn't been loaded, it looks to see if there is a loader registered for it. You can register a function to load a particular module. If a loader is found, a namespace for the module is created and the loader function is called. The loader function is given the name of the module and a namespace to insert it into. After the loader function completes, the process stops here.
  4. If there is no custom loader function for the module, the interpreter will attempt to load it by itself. It will look for a module file (either uncompiled source with the ".md" extension or binary compiled code with the ".mdm" extension). If it finds both a source file and a compiled file, it attempts to load the one with the most recent modification time. If it finds just one, it attempts to load it. If successful, the module's top-level function is called to initialize it, and the process stops here.
  5. This step is optional (and not currently implemented in the reference implementation): the interpreter will look for a dynamic library to load. It will attempt to load the library and call an initialization function to initialize the module.

The import syntax may seem a bit simple compared to the D import syntax, but this is because most of the functionality of the D import syntax either has no analogue in MiniD, or can be achieved through other means.

In D, there is 'static import', which forces you to use fully-qualified names to access module members, and 'regular' import, which allows you to access other module members unqualified as long as there is no ambiguity. In MiniD, since modules are globally loaded, accessing module members always has to be done through fully-qualified names.

Using local variable declarations, however, you can imitate much of the functionality of the D import statement. For example, you can rename imports by simply assigning the module name into a local:

import some.long.name;
local shortName = some.long.name;

Now all the members of some.long.name can be accessed through the shorter shortName. This can also boost performance if you access members of some.long.name a lot or in loops, since there will be much fewer global accesses and indexes.

D has selective imports, which is a way of making symbol-level imports from other modules. Because of the way imports work in MiniD, there is no real concept of selective imports, but there is a similar syntax which allows you to bring symbols from another module into the current module's namespace. The syntax:

import other : a, b, c;

Is equivalent to the following:

import other;
local a = other.a;
local b = other.b;
local c = other.c;

Note that because these are local variables and not references to the members of the other module, you should not use this syntax (really, either syntax) to selectively import non-reference type members from other modules.

D also has the concept of public and private imports. This has no meaning in MiniD, since imports are not symbolic, and there's nothing to stop code from accessing modules which it doesn't import. If module a imports module b, any dependencies that b has will be loaded and will be accessible from a, even if a doesn't import those modules. But in the interest of clarity, module a should import any modules on which it explicitly depends.

Lastly MiniD allows you to perform conditional importing, and even importing of modules with dynamically-generated names. See the Statements section for info on this style of import.