Improve this page Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using local clone. Page wiki View or edit the community-maintained wiki page associated with this page.

Modules

Module:
    ModuleDeclaration DeclDefs
    DeclDefs

DeclDefs:
    DeclDef
    DeclDef DeclDefs

DeclDef:
    AttributeSpecifier
    Declaration
    Constructor
    Destructor
    Postblit
    Allocator
    Deallocator
    Invariant
    UnitTest
    AliasThis
    StaticConstructor
    StaticDestructor
    SharedStaticConstructor
    SharedStaticDestructor
    ConditionalDeclaration
    DebugSpecification
    VersionSpecification
    StaticAssert
    TemplateDeclaration
    TemplateMixinDeclaration
    TemplateMixin
    MixinDeclaration
    ;

Modules have a one-to-one correspondence with source files. The module name is, by default, the file name with the path and extension stripped off, and can be set explicitly with the module declaration.

Modules automatically provide a namespace scope for their contents. Modules superficially resemble classes, but differ in that:

Modules can be grouped together in hierarchies called packages.

Modules offer several guarantees:

Module Declaration

The ModuleDeclaration sets the name of the module and what package it belongs to. If absent, the module name is taken to be the same name (stripped of path and extension) of the source file name.

ModuleDeclaration:
    ModuleAttributesopt module ModuleFullyQualifiedName ;

ModuleAttributes:
    ModuleAttribute
    ModuleAttribute ModuleAttributes

ModuleAttribute:
    DeprecatedAttribute
    UserDefinedAttribute

ModuleFullyQualifiedName:
    ModuleName
    Packages . ModuleName

ModuleName:
    Identifier

Packages:
    PackageName
    Packages . PackageName

PackageName:
    Identifier

The Identifiers preceding the rightmost are the Packages that the module is in. The packages correspond to directory names in the source file path. Package names cannot be keywords, hence the corresponding directory names cannot be keywords, either.

If present, the ModuleDeclaration appears syntactically first in the source file, and there can be only one per source file.

Example:

module c.stdio; // module stdio in the c package

By convention, package and module names are all lower case. This is because those names can have a one-to-one correspondence with the operating system's directory and file names, and many file systems are not case sensitive. All lower case package and module names will minimize problems moving projects between dissimilar file systems.

If the file name of a module is an invalid module name (e.g. foo-bar.d), you may use a module declaration to set a valid module name:

module foo_bar;

ModuleDeclaration can have an optional DeprecatedAttribute. The compiler will produce a message when the deprecated module is imported.

deprecated module foo;
module bar;
import foo;  // Deprecated: module foo is deprecated
DeprecatedAttribute can have an optional string argument to provide a more expressive message.
deprecated("Please use foo2 instead.")
module foo;
module bar;
import foo;  // Deprecated: module foo is deprecated - Please use foo2 instead.

Import Declaration

Symbols from one module are made available in another module by using the ImportDeclaration:

ImportDeclaration:
    import ImportList ;
    static import ImportList ;

ImportList:
    Import
    ImportBindings
    Import , ImportList

Import:
    ModuleFullyQualifiedName
    ModuleAliasIdentifier = ModuleFullyQualifiedName

ImportBindings:
    Import : ImportBindList

ImportBindList:
    ImportBind
    ImportBind , ImportBindList

ImportBind:
    Identifier
    Identifier = Identifier

ModuleAliasIdentifier:
    Identifier

There are several forms of the ImportDeclaration, from generalized to fine-grained importing.

The order in which ImportDeclarations occur has no significance.

ModuleFullyQualifiedNames in the ImportDeclaration must be fully qualified with whatever packages they are in. They are not considered to be relative to the module that imports them.

Basic Imports

The simplest form of importing is to just list the modules being imported:

import std.stdio; // import module stdio from package std
import foo, bar;  // import modules foo and bar

void main()
{
    writeln("hello!");  // calls std.stdio.writeln
}

How basic imports work is that first a name is searched for in the current namespace. If it is not found, then it is looked for in the imports. If it is found uniquely among the imports, then that is used. If it is in more than one import, an error occurs.

module A;
void foo();
void bar();
module B;
void foo();
void bar();
module C;
import A;
void foo();
void test()
{
    foo(); // C.foo() is called, it is found before imports are searched
    bar(); // A.bar() is called, since imports are searched
}
module D;
import A;
import B;
void test()
{
    foo();   // error, A.foo() or B.foo() ?
    A.foo(); // ok, call A.foo()
    B.foo(); // ok, call B.foo()
}
module E;
import A;
import B;
alias foo = B.foo;
void test()
{
    foo();   // call B.foo()
    A.foo(); // call A.foo()
    B.foo(); // call B.foo()
}

Public Imports

By default, imports are private. This means that if module A imports module B, and module B imports module C, then C's names are not searched for. An import can be specifically declared public, when it will be treated as if any imports of the module with the ImportDeclaration also import the public imported modules.

All symbols from a publicly imported module are also aliased in the importing module. This means that if module D imports module C, and module C publicly imports module B which has the symbol bar, in module D you can access the symbol via bar, B.bar, and C.bar.

module A;
void foo() { }
module B;
void bar() { }
module C;
import A;
public import B;
...
foo();  // call  A.foo()
bar();  // calls B.bar()
module D;
import C;
...
foo();	 // error, foo() is undefined
bar();	 // ok, calls B.bar()
B.bar(); // ditto
C.bar(); // ok, calls C.bar() which is an alias to B.bar()

Static Imports

Basic imports work well for programs with relatively few modules and imports. If there are a lot of imports, name collisions can start occurring between the names in the various imported modules. One way to stop this is by using static imports. A static import requires one to use a fully qualified name to reference the module's names:

static import std.stdio;

void main()
{
    writeln("hello!");           // error, writeln is undefined
    std.stdio.writeln("hello!"); // ok, writeln is fully qualified
}

Renamed Imports

A local name for an import can be given, through which all references to the module's symbols must be qualified with:

import io = std.stdio;

void main()
{
    io.writeln("hello!");        // ok, calls std.stdio.writeln
    std.stdio.writeln("hello!"); // error, std is undefined
    writeln("hello!");           // error, writeln is undefined
}

Renamed imports are handy when dealing with very long import names.

Selective Imports

Specific symbols can be exclusively imported from a module and bound into the current namespace:

import std.stdio : writeln, foo = write;

void main()
{
    std.stdio.writeln("hello!"); // error, std is undefined
    writeln("hello!");           // ok, writeln bound into current namespace
    write("world");              // error, write is undefined
    foo("world");                // ok, calls std.stdio.write()
    fwritefln(stdout, "abc");    // error, fwritefln undefined
}

static cannot be used with selective imports.

Renamed and Selective Imports

When renaming and selective importing are combined:

import io = std.stdio : foo = writeln;

void main()
{
    writeln("bar");           // error, writeln is undefined
    std.stdio.foo("bar");     // error, foo is bound into current namespace
    std.stdio.writeln("bar"); // error, std is undefined
    foo("bar");               // ok, foo is bound into current namespace,
                              // FQN not required
    io.writeln("bar");        // ok, io=std.stdio bound the name io in
                              // the current namespace to refer to the entire module
    io.foo("bar");            // error, foo is bound into current namespace,
                              // foo is not a member of io
}

Scoped Imports

Import declarations may be used at any scope. For example:

void main()
{
    import std.stdio;
    writeln("bar");
}

The imports are looked up to satisfy any unresolved symbols at that scope. Imported symbols may hide symbols from outer scopes.

In function scopes, imported symbols only become visible after the import declaration lexically appears in the function body. In other words, imported symbols at function scope cannot be forward referenced.

void main()
{
    void writeln(string) {}
    void foo()
    {
        writeln("bar"); // calls main.writeln
        import std.stdio;
        writeln("bar"); // calls std.stdio.writeln
        void writeln(string) {}
        writeln("bar"); // calls main.foo.writeln
    }
    writeln("bar"); // calls main.writeln
    std.stdio.writeln("bar");  // error, std is undefined
}

Module Scope Operator

Sometimes, it's necessary to override the usual lexical scoping rules to access a name hidden by a local name. This is done with the global scope operator, which is a leading ‘.’:
int x;

int foo(int x)
{
    if (y)
        return x;  // returns foo.x, not global x
    else
        return .x; // returns global x
}
The leading ‘.’ means look up the name at the module scope level.

Static Construction and Destruction

Static constructors are code that gets executed to initialize a module or a class before the main() function gets called. Static destructors are code that gets executed after the main() function returns, and are normally used for releasing system resources.

There can be multiple static constructors and static destructors within one module. The static constructors are run in lexical order, the static destructors are run in reverse lexical order.

Static constructors and static destructors run on thread local data, and are run whenever threads are created or destroyed.

Shared static constructors and shared static destructors are run on global shared data, and constructors are run once on program startup and destructors are run once on program termination.

Order of Static Construction

Shared static constructors on all modules are run before any static constructors.

The order of static initialization is implicitly determined by the import declarations in each module. Each module is assumed to depend on any imported modules being statically constructed first. Other than following that rule, there is no imposed order on executing the module static constructors.

Cycles (circular dependencies) in the import declarations are allowed as long as not both of the modules contain static constructors or static destructors. Violation of this rule will result in a runtime exception.

Order of Static Construction within a Module

Within a module, the static construction occurs in the lexical order in which they appear.

Order of Static Destruction

It is defined to be exactly the reverse order that static construction was performed in. Static destructors for individual modules will only be run if the corresponding static constructor successfully completed.

Shared static destructors are executed after static destructors.

Order of Unit tests

Unit tests are run in the lexical order in which they appear within a module.

Mixin Declaration

MixinDeclaration:
    mixin ( AssignExpression ) ;

The AssignExpression must evaluate at compile time to a constant string. The text contents of the string must be compilable as a valid DeclDefs, and is compiled as such.

Package Module

A package module can be used to publicly import other modules, while enabling a simpler import syntax. It enables converting a module into a package of modules, without breaking existing code which uses that module. Example of a set of library modules:

libweb/client.d:

module libweb.client;

void runClient() { }

libweb/server.d:

module libweb.server;

void runServer() { }

libweb/package.d:

module libweb;

public import libweb.client;
public import libweb.server;

The package module must have the file name package.d. The module name is declared to be the fully qualified name of the package. Package modules can be imported just like any other modules:

test.d:

module test;

// import the package module
import libweb;

void main()
{
    runClient();
    runServer();
}

A package module can be nested inside of a sub-package:

libweb/utils/package.d:

// must be declared as the fully qualified name of the package, not just 'utils'
module libweb.utils;

// publicly import modules from within the 'libweb.utils' package.
public import libweb.utils.conv;
public import libweb.utils.text;

The package module can then be imported with the standard module import declaration:

test.d:

module test;

// import the package module
import libweb.utils;

void main() { }