Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

Modules and Libraries

The building blocks of D programs (and libraries) are modules.

D modules are based on a simple concept: Every source file is a module. Accordingly, the single files that we have been writing our programs in have all been individual modules.

By default, the name of a module is the same as its filename without the .d extension. When explicitly specified, the name of the module is defined by the module keyword, which must appear as the first non-comment line in the source file.

For example, assuming that the name of a source file is "cat.d", the name of the module would be specified by the module keyword:

module cat;

class Cat {
    // ...

The module line is optional if the module is not part of any package (see below). When not specified, it is the same as the file name without the .d extension.

static this() and static ~this()

static this() and static ~this() at module scope are similar to their struct and class counterparts:

module cat;

static this() {
    // ... the initial operations of the module ...

static ~this() {
    // ... the final operations of the module ...

Code that are in these scopes are executed once for each thread. (Note that most programs consist of a single thread that starts executing the main() function.) Code that should be executed only once for the entire program (e.g. initializing shared and immutable variables) must be defined in shared static this() and shared static ~this() blocks, which will be covered in the Data Sharing Concurrency chapter.

File and module names

D supports Unicode in source code and module names. However, the Unicode support of file systems vary. For example, although most Linux file systems support Unicode, the file names in Windows file systems may not distinguish between lower and upper case letters. Additionally, most file systems limit the characters that can be used in file and directory names.

For portability reasons, I recommend that you use only lower case ASCII letters in file names. For example, "resume.d" would be a suitable file name for a class named Résumé.

Accordingly, the name of the module would consist of ASCII letters as well:

module resume;  // Module name consisting of ASCII letters

class Résumé {  // Program code consisting of Unicode characters
    // ...

A combination of related modules are called a package. D packages are a simple concept as well: The source files that are inside the same directory are considered to belong to the same package. The name of the directory becomes the name of the package, which must also be specified as the first parts of module names.

For example, if "cat.d" and "dog.d" are inside the directory "animal", then specifying the directory name along with the module name makes them be a part of the same package:


class Cat {
    // ...

Similarly, for the dog module:


class Dog {
    // ...

For modules that are parts of packages, the module line is not optional and the whole module name including the package name must be specified.

Since package names correspond to directory names, the package names of modules that are deeper than one directory level must reflect that hierarchy. For example, if the "animal" directory included a "vertebrate" directory, the name of a module inside that directory would include vertebrate as well:


The directory hierarchies can be arbitrarily complex depending on the needs of the program. Relatively short programs usually have all of their source files in a single directory.

Using modules in programs

The import keyword, which we have been using in almost every program so far, is for introducing a module to the current module:

import std.stdio;

The module name may contain the package name as well. For example, the std. part above indicates that stdio is a module that is a part of the std package.

The and modules would be imported similarly. Let's assume that the following code is inside a file named "deneme.d":

module deneme;        // the name of this module

import;    // a module that it uses
import;    // another module that it uses

void main() {
    auto cat = new Cat();
    auto dog = new Dog();

Note: As described below, for the program to be built correctly, those module files must also be provided to the linker.

More than one module can be imported at the same time:

Selective imports

Instead of importing a module as a whole with all of its names, it is possible to import just specific names from it.

import std.stdio : writeln;

// ...

    writefln("Hello %s.", name);    // ← compilation ERROR

The code above cannot be compiled because only writeln is imported, not writefln.

Selective imports are considered to be better than importing an entire module because it reduces the chance of name collisions. As we will see in an example below, a name collision can occur when the same name appears in more than one imported module.

Selective imports may reduce compilation times as well because the compiler needs to compile only the parts of a module that are actually imported. On the other hand, selective imports require more work as every imported name must be specified separately on the import line.

This book does not take advantage of selective imports mostly for brevity.

Local imports

So far we have always imported all of the required modules at the tops of programs:

import std.stdio;     // ← at the top
import std.string;    // ← at the top

// ... the rest of the module ...

Instead, modules can be imported at any other line of the source code. For example, the two functions of the following program import the modules that they need in their own scopes:

string makeGreeting(string name) {
    import std.string;

    string greeting = format("Hello %s", name);
    return greeting;

void interactWithUser() {
    import std.stdio;

    write("Please enter your name: ");
    string name = readln();

void main() {

Local imports are recommended over global imports because instead of importing every module unconditionally at the top, the compiler can import only the ones that are in the scopes that are actually used. If the compiler knows that the program never calls a function, it can ignore the import directives inside that function.

Additionally, a locally imported module is accessible only inside that local scope, further reducing the risk of name collisions.

We will later see in the Mixins chapter that local imports are in fact required for template mixins.

The examples throughout this book do not take advantage of local imports mostly because local imports were added to D after the start of writing this book.

Locations of modules

The compiler finds the module files by converting the package and module names directly to directory and file names.

For example, the previous two modules would be located as "animal/cat.d" and "animal/dog.d", respectively (or "animal\cat.d" and "animal\dog.d", depending on the file system). Considering the main source file as well, the program above consists of three files.

Long and short module names

The names that are used in the program may be spelled out with the module and package names:

    auto cat0 = Cat();
    auto cat1 =;   // same as above

The long names are normally not needed but sometimes there are name conflicts. For example, when referring to a name that appears in more than one module, the compiler cannot decide which one is meant.

The following program is spelling out the long names to distinguish between two separate Jaguar structs that are defined in two separate modules: animal and car:


// ...

    auto conflicted =  Jaguar();            // ← compilation ERROR

    auto myAnimal =; // ← compiles
    auto myCar    =; // ← compiles
Renamed imports

It is possible to rename imported modules either for convenience or to resolve name conflicts:

import carnivore =;
import vehicle =;

// ...

    auto myAnimal = carnivore.Jaguar();       // ← compiles
    auto myCar    = vehicle.Jaguar();         // ← compiles

Instead of renaming the entire import, it is possible to rename individual imported symbols.

For example, when the following code is compiled with the -w compiler switch, the compiler would warn that sort() function should be preferred instead of .sort property:

import std.stdio;
import std.algorithm;

// ...

    auto arr = [ 2, 10, 1, 5 ];
    arr.sort;    // ← compilation WARNING
Warning: use std.algorithm.sort instead of .sort property

Note: The arr.sort expression above is the equivalent of sort(arr) but it is written in the UFCS syntax, which we will see in a later chapter.

One solution in this case is to import std.algorithm.sort by renaming it. The new name algSort below means the sort() function and the compiler warning is eliminated:

import std.stdio;
import std.algorithm : algSort = sort;

void main() {
    auto arr = [ 2, 10, 1, 5 ];
Importing a package as a module

Sometimes multiple modules of a package may need to be imported together. For example, whenever one module from the animal package is imported, all of the other modules may need to be imported as well:,,, etc.

In such cases it is possible to import some or all of the modules of a package by importing the package as if it were a module:

import animal;    // ← entire package imported as a module

It is achieved by a special configuration file in the package directory, which must always be named as package.d. That special file includes the module directive for the package and imports the modules of the package publicly:

// The contents of the file animal/package.d:
module animal;

public import;
public import;
public import;
// ... same for the other modules ...

Importing a module publicly makes that module available to the users of the importing module as well. As a result, when the users import just the animal module (which actually is a package), they get access to and all the other modules as well.

Adding module definitions to the program

The import keyword is not sufficient to make modules become parts of the program. It simply makes available the features of a module inside the current module. That much is needed only to compile the code.

It is not possible to build the previous program only by the main source file, "deneme.d":

$ dmd deneme.d -w
deneme.o: In function `_Dmain':
deneme.d: undefined reference to `_D6animal3cat3Cat7__ClassZ'
deneme.d: undefined reference to `_D6animal3dog3Dog7__ClassZ'
collect2: ld returned 1 exit status

Those error messages are generated by the linker. Although they are not user-friendly messages, they indicate that some definitions that are needed by the program are missing.

The actual build of the program is the responsibility of the linker, which gets called automatically by the compiler behind the scenes. The compiler passes the modules that it has just compiled to the linker, and the linker combines those modules (and libraries) to produce the executable program.

For that reason, all of the modules that make up the program must be provided to the linker. For the program above to be built, "animal/cat.d" and "animal/dog.d" must also be specified on the compilation line:

$ dmd deneme.d animal/cat.d animal/dog.d -w

Instead of having to mention the modules individually every time on the command line, they can be combined as libraries.


A collection of compiled modules is called a library. Libraries are not programs themselves; they do not have the main() function. Libraries contain compiled definitions of functions, structs, classes, and other features of modules, which are to be linked later by the linker to produce the program.

dmd's -lib command line option is for making libraries. The following command makes a library that contains the "cat.d" and the "dog.d" modules. The name of the library is specified by the -of switch:

$ dmd animal/cat.d animal/dog.d -lib -ofanimal -w

The actual name of the library file depends on the platform. For example, the extension of library files is .a under Linux systems: animal.a.

Once that library is built, It is not necessary to specify the "animal/cat.d" and "animal/dog.d" modules individually anymore. The library file is sufficient:

$ dmd deneme.d animal.a -w

The command above replaces the following one:

$ dmd deneme.d animal/cat.d animal/dog.d -w

As an exception, the D standard library Phobos need not be specified on the command line. That library is automatically included behind the scenes. Otherwise, it could be specified similar to the following line:

$ dmd deneme.d animal.a /usr/lib64/libphobos2.a -w

Note: The name and location of the Phobos library may be different on different systems.