Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Language Atlas (mini-courses for more languages)  Module 9 – D for C, C++ and Python programmers

D: compilers, dub and where D is used

Install LDC 1.43, build a D file and a dub project with a unit test, choose between DMD, LDC and GDC, find modules in Phobos and see where D is used.

  • Beginner
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, LDC 1.43.0 and dub 1.42.0
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Install LDC 1.43 in a folder you own, then build and run a single D file and a dub project
  • Choose between DMD, LDC and GDC for a machine and name the front-end version each one uses
  • Find the Phobos module for a task, from std.stdio to std.algorithm
  • Explain where D is used and what the list of organisations using it can and cannot show

Before you start

What you’ll learn · Before you start

What you will learn

  • Install LDC 1.43 in a folder you own, then build and run a single D file and a dub project
  • Choose between DMD, LDC and GDC for a machine and name the front-end version each one uses
  • Find the Phobos module for a task, from std.stdio to std.algorithm
  • Explain where D is used and what the list of organisations using it can and cannot show

Before you start

On this page

Three compilers read D, and the reference one, DMD, lists no build for a Mac with Apple silicon on its download page. So this mini-course runs every example with LDC 1.43.0 and its build tool, dub 1.42.0, on such a Mac. A first program says which compiler built it, and a first project hides a test that dub never runs.

Here you install LDC in a folder you own, build a single file and then a dub project with a unit test, choose a compiler for your own machine, find your way round Phobos, D’s standard library, and read the list of organisations that use D for what it can and cannot tell you.

LDC’s releases are archives to unpack, one per system, and installing one needs no administrator rights. The v1.43.0 release lists ldc2-1.43.0-osx-arm64.tar.xz for a Mac with Apple silicon, beside archives for Linux, Windows, FreeBSD and Android, and one file of SHA-256 sums for all of them. The package’s README asks for one thing beside it, a C compiler on the PATH, because LDC links every program through it. On a Mac that is Apple’s clang, from the Command Line Tools that xcode-select --install installs. Step through the install, with the two checks that can send you back:

Interactive · stage by stage From LDC's release tarball to a first program Step through installing LDC 1.43.0 into a folder you own and running hello.d, with the two checks that send you back.
From LDC's release tarball to a first program, step 8 of 8. Showing Download the tarball, Compare its SHA-256, Unpack it, Put its bin on PATH, Ask ldc2 --version, ldc2 -run hello.d. In focus: ldc2 -run hello.d.hash differsnot 1.43.0Download the tarballldc2-1.43.0-osx-arm64Compare its SHA-256listed: 5e332349…Unpack itinto a folder you ownPut its bin on PATHldc2, dub, ldmd2Ask ldc2 --versionmust say 1.43.0ldc2 -run hello.dcompile, link, run

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Pick the package. The v1.43.0 release of LDC lists one archive per system. On a Mac with Apple silicon it is ldc2-1.43.0-osx-arm64.tar.xz; Linux, Windows, FreeBSD and Android have their own.
  2. Compare the hash. The release also lists ldc2-1.43.0.sha256sums.txt. Run shasum -a 256 on your download: for the macOS arm64 archive the listed value starts 5e332349. Any other value means another or a damaged file.
  3. The hash differs. Download the archive again and compare once more. Unpack nothing whose hash does not match the list.
  4. Unpack. tar -xJf unpacks a folder named ldc2-1.43.0-osx-arm64, with bin, import, lib and etc inside. Any folder you own will do; nothing needs administrator rights.
  5. The PATH. Put that folder's bin on your PATH. It holds ldc2, the compiler, and also dub, the build tool, and ldmd2, which takes the options of the DMD compiler.
  6. Check the version. ldc2 --version must start with LDC - the LLVM D compiler (1.43.0), then the DMD v2.113.0 front end and LLVM 22.1.8 it is built on, as in the recorded session.
  7. Another ldc2 comes first. Another version means another ldc2 is found first on your PATH, perhaps from a package manager. Move your folder to the front of PATH before you compare any output.
  8. Run a first program. ldc2 -run hello.d compiles hello.d, links it through the C compiler on the PATH into a temporary program, runs it and removes it. hello.d printed LDC as the vendor and 2113 as the front-end version.

Hash Generator Drop the downloaded archive here and paste its line from ldc2-1.43.0.sha256sums.txt to check that the file matches.

The recorded session starts with that folder’s bin on the PATH. This is the program it builds. __VENDOR__ and __VERSION__ are special tokens that the compiler fills in, so the program reports what built it:

hello.d, a first D program D · first/hello.d
// A first D program. __VENDOR__ and __VERSION__ are filled in by the compiler,
// so the program says which compiler built it and which version of the
// language's front end that compiler uses.
import std.stdio;

void main()
{
    writeln("Hello from D, built by ", __VENDOR__);
    writeln("Front end version: ", __VERSION__);
    writeln("40 + 2 = ", 40 + 2);
}
Versions, a run in one step, a build and its sizes Python · first_session.py
from session import run

# The first session of the D mini-course: ask LDC and dub for their versions, compile and run a first program in one
# step and list the folder after it, then build it as a program of its own, run it, and compare the sizes of the object
# file, the default build and the optimised build.
run("""
cd first
ldc2 --version | head -3
dub --version | cut -d, -f1
ldc2 -run hello.d
ls -1
ldc2 hello.d
ls -1
./hello
wc -c < hello.o
wc -c < hello
ldc2 -O hello.d -of=hello_fast
wc -c < hello_fast
""")

Output

$ cd first
$ ldc2 --version | head -3
LDC - the LLVM D compiler (1.43.0):
  based on DMD v2.113.0 and LLVM 22.1.8
  built with LDC - the LLVM D compiler (1.43.0)
$ dub --version | cut -d, -f1
DUB version 1.42.0
$ ldc2 -run hello.d
Hello from D, built by LDC
Front end version: 2113
40 + 2 = 42
$ ls -1
hello.d
$ ldc2 hello.d
$ ls -1
hello
hello.d
hello.o
$ ./hello
Hello from D, built by LDC
Front end version: 2113
40 + 2 = 42
$ wc -c < hello.o
   24808
$ wc -c < hello
 1188488
$ ldc2 -O hello.d -of=hello_fast
$ wc -c < hello_fast
 1183896

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 first_session.py

Read it from the top. ldc2 --version names three versions: LDC’s own, 1.43.0, the toolchain pin: The exact version of the compiler, interpreter or SDK that an example was built and run with, such as Tcl 9.0.4 or the .NET SDK 10.0.401. Another version may print something else for the same file. More in “How the Language Atlas works” of every example in this mini-course; the DMD v2.113.0 front end (D compiler): The part of a D compiler that reads the source, checks it and works out what it means, before a back end turns it into machine code. DMD, LDC and GDC share DMD's front end, and __VERSION__ gives its version as a number: 2113 for 2.113. it is built on; and LLVM 22.1.8, the back end that turns the checked program into machine code. The program agrees: __VENDOR__ gave LDC, and __VERSION__ gave 2113, the front end’s version 2.113 written as a whole number. dub came in the same archive and reported 1.42.0.

ldc2 -run hello.d compiled, linked and ran the program in one step and then removed it, which suits a quick try: the ls -1 right after it lists only hello.d. The second ls -1, after ldc2 hello.d, lists what a normal build leaves: the object file hello.o, 24,808 bytes, and the program hello, 1,188,488 bytes. Nearly all of the program is library code it links in, the D runtime and the parts of Phobos that writeln needs, which is also why -O, the optimised build, saved only 4,592 bytes here.

Repeat the first build

With LDC 1.43.0’s bin folder first on your PATH, save the program as hello.d and type ldc2 -run hello.d. The second line must say 2113; another number means another compiler, or another release of LDC, came first on your PATH.

Checkpoint

Check: Installing LDC and reading a first build

  1. Question 1 of 2 hello.d printed "Front end version: 2113" when LDC 1.43.0 built it. What does 2113 stand for?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The DMD 2.113 front end that LDC 1.43.0 is built on

    The D specification calls __VERSION__ the compiler version as an integer. LDC fills it in with the version of its DMD front end, 2.113, as 2113; __VENDOR__ says which compiler it is, LDC.

  2. Question 2 of 2 The session ran ldc2 -run hello.d first and ldc2 hello.d after it. Which files did ldc2 -run leave in the folder?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: None: it ran a program it built for the purpose and removed it

    ldc2 -run compiles, links and runs in one step and cleans up after itself, which suits a quick try. A normal build leaves the object file, 24,808 bytes here, and the program, 1,188,488 bytes.

dub builds a project from its recipe

A program with several files, tests or packages written by others is built with dub. Its dub recipe: The file that describes a dub package, dub.json or dub.sdl: its name, description, licence, target type and dependencies. dub reads it before every build and refuses one it cannot accept, such as a name with a space., a file named dub.json or dub.sdl, names the package and says what to build, and the sources go in a folder named source, which dub uses when the recipe names no other. dub init -n makes an empty project; the session then turns to a small one written by hand, wordfreq:

dub init, then a project built, run and tested Python · dub_session.py
from session import run

# dub, the build tool that comes with LDC: make an empty project with dub init and run it, then build, test and run a
# small project whose recipe, dub.json, was written by hand. dub init also writes an author into its dub.json, the
# name of your account, so this session shows that project's files but not that recipe.
run("""
dub init -n greet
ls -a greet greet/source
cat greet/source/app.d
dub run --root=greet
cd wordfreq
cat dub.json
find source -name '*.d' | sort
dub run -- To be or not to be
dub test
dub build --build=release
./wordfreq the cat and the hat
""")

Output

$ dub init -n greet
     Success created empty project in greet
             Package successfully created in greet
$ ls -a greet greet/source
greet:
.		..		.gitignore	dub.json	source

greet/source:
.	..	app.d
$ cat greet/source/app.d
import std.stdio;

void main()
{
	writeln("Edit source/app.d to start your project.");
}
$ dub run --root=greet
    Starting Performing "debug" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building greet ~master: building configuration [application]
     Linking greet
     Running greet/greet
Edit source/app.d to start your project.
$ cd wordfreq
$ cat dub.json
{
	"name": "wordfreq",
	"description": "Counts how often each word occurs in its arguments.",
	"license": "BSL-1.0",
	"targetType": "executable"
}
$ find source -name '*.d' | sort
source/app.d
source/wordfreq/count.d
$ dub run -- To be or not to be
    Starting Performing "debug" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building wordfreq ~master: building configuration [application]
     Linking wordfreq
     Running wordfreq To be or not to be
be 2
not 1
or 1
to 2
$ dub test
             Generating test runner configuration 'wordfreq-test-application' for 'application' (executable).
     Warning Excluding main source file source/app.d from test.
    Starting Performing "unittest" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building wordfreq ~master: building configuration [wordfreq-test-application]
     Linking wordfreq-test-application
     Running wordfreq-test-application
1 modules passed unittests
$ dub build --build=release
    Starting Performing "release" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building wordfreq ~master: building configuration [application]
     Linking wordfreq
$ ./wordfreq the cat and the hat
and 1
cat 1
hat 1
the 2

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dub_session.py

dub init -n greet made a folder with three entries, .gitignore, the recipe dub.json and source/app.d, a program whose one line asks you to edit it, and dub run --root=greet built and ran it. dub init also writes the name of your user account into the recipe as its author, so the session shows the recipe of wordfreq instead: a name, a description, a licence and "targetType": "executable". Its code is in two files: source/app.d holds main, and source/wordfreq/count.d holds countWords with a unittest block. Step through the run:

Interactive · message by message What dub run does in the wordfreq folder Step through one dub run, from the command you type to the four lines the program prints.
What dub run does in the wordfreq folder, step 6 of 6. Messages so far: You to dub 1.42.0: dub run -- To be…; dub 1.42.0 to ldc2 1.43.0: compile app.d, count.d; ldc2 1.43.0 to dub 1.42.0: program wordfreq; dub 1.42.0 to You: be 2 · not 1 · or 1 · to 2.Youin the wordfreq folderdub 1.42.0no dependenciesldc2 1.43.0the compilerdub run -- To be…compile app.d, count.dprogram wordfreqbe 2 · not 1 · or 1 · to 2

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The command. You type dub run -- To be or not to be. Everything before -- is for dub; the five words after it are the arguments of the program.
  2. The recipe. dub reads dub.json: the package name wordfreq, a description, the licence and targetType executable. A recipe it cannot accept stops here, before anything is compiled.
  3. Nothing to fetch. The recipe lists no dependencies, so dub has no package to fetch from the registry and needs no network for this build.
  4. Compile. dub hands every .d file under source/ to ldc2 in one call, for a debug build, the default build type: Performing "debug" build in the session.
  5. Link. ldc2 compiles both files into one object file, then links the program. dub keeps the build in its cache in your home folder, ~/.dub/cache, and puts the program, wordfreq, in the project folder.
  6. Run. dub starts ./wordfreq with the five words. It printed be 2, not 1, or 1 and to 2: lower-cased, counted, and sorted by word.

The other two commands use dub’s build type (dub): A named set of compiler settings that dub applies to a build: debug, the default, keeps debug information; release turns on release mode, optimisation and inlining; unittest compiles the unittest blocks in.. dub test made a build of the unittest type and ran its tests, which printed 1 modules passed unittests. dub build --build=release made a build of the release type, which dub’s reference describes as release mode, optimisation and inlining; debug is the default.

Why are the code and its test in a module of their own? Because of a line that dub test printed: Excluding main source file source/app.d from test. dub’s test build brings a main of its own, so it leaves out the file with yours, and every unittest block in that file with it. The next session puts one wrong test, which expects “the” three times in “The cat and the hat”, first in app.d and then in a module:

One wrong test, in app.d and in a module Python · testtrap_session.py
from session import run

# The same wrong unittest block twice: in source/app.d, the file with main, and in a module of its own. dub test leaves
# the main file out of its test build, so only the second one is run.
run("""
cd testtrap/inapp
grep -n 'assert' source/app.d
dub test
cd ../inmodule
grep -n 'assert' source/wordfreq/count.d
!dub test
""")

Output

$ cd testtrap/inapp
$ grep -n 'assert' source/app.d
18:    assert(counts["the"] == 3); // wrong on purpose: "the" occurs twice
$ dub test
             Generating test runner configuration 'wordfreq-test-application' for 'application' (executable).
     Warning Excluding main source file source/app.d from test.
    Starting Performing "unittest" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building wordfreq ~master: building configuration [wordfreq-test-application]
     Linking wordfreq-test-application
     Running wordfreq-test-application
All unit tests have been run successfully.
$ cd ../inmodule
$ grep -n 'assert' source/wordfreq/count.d
17:    assert(counts["the"] == 3); // wrong on purpose: "the" occurs twice
$ dub test
             Generating test runner configuration 'wordfreq-test-application' for 'application' (executable).
     Warning Excluding main source file source/app.d from test.
    Starting Performing "unittest" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building wordfreq ~master: building configuration [wordfreq-test-application]
     Linking wordfreq-test-application
     Running wordfreq-test-application
core.exception.AssertError@source/wordfreq/count.d(17): Assertion failure
1/1 modules FAILED unittests
Error Program exited with code 1
[exit status 2]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 testtrap_session.py

Tests in the file with main

With the wrong test in app.d, dub test printed All unit tests have been run successfully. and exited with 0, although nothing was tested. In source/wordfreq/count.d the same test failed with an AssertError at line 17. Keep main small and put the code, with its tests, in modules; then 1 modules passed unittests shows that tests ran.

dub refuses some recipes and only warns about others. Here are five that it warned about or refused:

Five recipes that dub warns about or refuses Python · recipes_session.py
from session import run

# Five recipes that dub warns about or refuses, each in a folder of its own with an empty program in source/app.d. The
# last one has two problems, to show which one dub reports.
run("""
cd recipes/upper
cat dub.json
dub build
cd ../spaced
cat dub.json
!dub build
cd ../target
cat dub.json
!dub build
cd ../nameless
cat dub.json
!dub build
cd ../both
cat dub.json
!dub build
""")

Output

$ cd recipes/upper
$ cat dub.json
{
	"name": "WordFreq"
}
$ dub build
     Warning DUB package names should always be lower case. Please modify the "name" field in recipes/upper/dub.json accordingly. You can then add '"targetName": "WordFreq"' to keep the current executable name.
    Starting Performing "debug" build using …/bin/ldc2 for aarch64, arm_hardfloat.
    Building WordFreq ~master: building configuration [application]
     Linking WordFreq
$ cd ../spaced
$ cat dub.json
{
	"name": "word freq"
}
$ dub build
     Warning DUB package names may only contain alphanumeric characters, as well as '-' and '_'. Please modify the "name" field in recipes/spaced/dub.json accordingly. You can then add '"targetName": "word freq"' to keep the current executable name.
Error Aborting due to the package name containing spaces.
[exit status 2]
$ cd ../target
$ cat dub.json
{
	"name": "wordfreq",
	"targetType": "program"
}
$ dub build
       Error recipes/target/dub.json(3:16): targetType: Expected to be a value of type TargetType, but is a scalar
[exit status 1]
$ cd ../nameless
$ cat dub.json
{
	"description": "A project without a name"
}
$ dub build
       Error recipes/nameless/dub.json(1:1): name: Required key was not found in configuration or command line arguments
[exit status 1]
$ cd ../both
$ cat dub.json
{
	"name": "WordFreq",
	"targetType": "program"
}
$ dub build
       Error recipes/both/dub.json(3:16): targetType: Expected to be a value of type TargetType, but is a scalar
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 recipes_session.py

An upper-case name only drew a warning, and the program was built as WordFreq. A space in the name stopped the build, program is not one of dub’s target types, and a recipe without a name was refused before anything else was checked. The last recipe has an upper-case name and an unknown target type, and dub reported only the target type: it reads every field of a recipe before it looks at how the name is spelt.

Checkpoint

Check: A dub project, its recipe and its tests

  1. Question 1 of 2 In "dub run -- To be or not to be", who receives the word To?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The program wordfreq, as its first argument

    dub run builds the project and then runs it, and -- separates dub's own options from the arguments of the program. The session ran wordfreq with To be or not to be and it printed be 2, not 1, or 1 and to 2.

  2. Question 2 of 2 A project has main and all its unittest blocks in source/app.d. dub test prints "All unit tests have been run successfully." What do you conclude?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing about the tests: dub test left app.d out, so none of them ran

    dub's test build has a main of its own, so it excludes the main source file, and every unittest block in it. Moved into source/wordfreq/count.d, the same wrong test failed with an AssertError. "1 modules passed unittests" is the line that shows tests ran.

Three compilers share one front end

The three D compilers share the front end, the part that reads D and checks it, and differ in the back end behind it. DMD, the reference compiler, has its own back end; its download page calls it the latest D version, very fast to compile with, and lists builds for i386 and amd64. LDC puts LLVM behind DMD’s front end and GDC puts GCC behind it, and for both the page names strong optimisation. Each DMD release numbers the language, its runtime and Phobos together, and the other two follow:

Timeline · 6 events in order One front end, three compilers Walk through the releases in order and watch the front-end version that each compiler builds on.
  1. DMD 2.000

    The D2 line of the language starts front end

    Version 2.000 of DMD, the reference compiler, began the D2 versions that every current compiler implements. Each DMD release since then numbers the language, its runtime and Phobos together.

  2. GCC 9

    GDC becomes part of GCC GDC

    GCC 9 added the D language to GCC, implementing version 2.076 of the language and run-time library. From then on a GCC install can include gdc beside gcc and g++.

  3. GCC 15

    GDC moves to 2.111.0 GDC

    GCC 15 updated its D front end and run-time library to version 2.111.0. GDC follows the front end with each GCC release, so it can trail the newest DMD by a few versions.

  4. GCC 16

    GDC takes a beta of 2.113.0 GDC

    GCC 16 took the D front end at 2.113.0-beta.1, a test release of 2.113.0, as the VERSION file of its D sources says; its release notes have no section on D. Which GDC you have depends on the GCC release your system packages.

  5. DMD 2.113.0

    The newest front end front end

    DMD 2.113.0 is the version the D download page offers. A program can read the front end it was built with: __VERSION__ gives 2113 for 2.113.

  6. LDC 1.43.0

    LDC on the 2.113.0 front end LDC

    LDC 1.43.0 takes the DMD 2.113.0 front end, druntime and Phobos, and puts LLVM 22.1.8 behind them in its prebuilt packages. hello.d built with it printed 2113 and the vendor LDC.

The timeline has 6 events, from DMD 2.000 to LDC 1.43.0.

So a feature that arrived in 2.112 or 2.113 builds with DMD 2.113.0 and LDC 1.43.0, but not with the GDC of GCC 15, which implements 2.111.0. GCC 16 moved GDC on to 2.113.0-beta.1, a test release of the newest front end, so the version a GDC has is that of the GCC release it came with. GDC has a strength of its own: the GDC project calls it part of GCC, with official packages for several Linux distributions, so on Linux it comes from the distribution’s own packages, named gdc or gcc-d.

LDC also ships ldmd2, a command that takes DMD’s options, so a build script written for DMD runs with LDC. With -vdmd it prints the ldc2 command it runs:

ldmd2 turns DMD's options into LDC's Python · compilers_session.py
from session import run

# ldmd2, which comes with LDC, takes the options of DMD, the reference compiler, and runs ldc2 with LDC's own options;
# -vdmd makes it print the ldc2 command it runs.
run("""
cd first
ldmd2 -vdmd -O -release -inline hello.d | head -1
./hello
""")

Output

$ cd first
$ ldmd2 -vdmd -O -release -inline hello.d | head -1
 -- Invoking: …/bin/ldc2 -ldmd -O -release -enable-inlining -Hkeep-all-bodies hello.d
$ ./hello
Hello from D, built by LDC
Front end version: 2113
40 + 2 = 42

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compilers_session.py

DMD’s -inline became LDC’s -enable-inlining, while -O and -release passed through as they were. Which compiler fits depends on your machine and on what you need most. Answer the three questions:

Interactive · answer and compare Which D compiler fits your machine and project? Answer the three questions about your machine and project; the table marks the compilers that fit and says why.
Which machine do you build on?
What matters more while you learn and work?
Does your code need language features from the newest releases?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

DMD, LDC and GDC against five things a project can need
Compiler A Mac with Apple silicon Builds while you edit Speed of the program The newest front end Other targets
DMD 2.113.0 Back end: its own Poor the download page lists i386 and amd64 builds only Good the download page calls its compilation very fast Mixed the download page claims strong optimization for LDC and GDC, not DMD Good the reference compiler, which the download page calls the latest D version Poor x86 builds only on the download page
LDC 1.43.0 Back end: LLVM 22.1.8 Good the release has an osx-arm64 package, used for every run here Mixed the download page claims very fast compilation for DMD, not LDC Good LLVM optimizes; the download page names strong optimization Good 1.43.0 is built on the 2.113.0 front end Good 1.43.0 adds packages to build for Windows, Android and WebAssembly
GDC in GCC 15 or 16 Back end: GCC Poor the GDC downloads page names Linux packages and an unofficial Windows build, no macOS Mixed the download page claims very fast compilation for DMD, not GDC Good GCC optimizes; the download page names strong optimization Mixed GCC 15 implements 2.111.0, two versions behind; GCC 16 a beta of 2.113.0 Good the download page lists i386, amd64, x32, armel, armhf and others
How the answers decide
  • “Which machine do you build on?” A Mac with Apple silicon: rules out DMD 2.113.0 and GDC in GCC 15 or 16. The download page lists DMD builds for i386 and amd64 only, and the GDC downloads page names Linux packages and an unofficial Windows build; LDC ships an osx-arm64 one.
  • “What matters more while you learn and work?” Fast builds while I edit and “Which machine do you build on?” Linux or Windows on x86-64: adds a caveat to LDC 1.43.0 and GDC in GCC 15 or 16. The download page calls DMD very fast to compile with, and says that of neither LDC nor GDC.
  • “What matters more while you learn and work?” The fastest program: adds a caveat to DMD 2.113.0. The download page names strong optimization for LDC and GDC, whose back ends are LLVM and GCC.
  • “Does your code need language features from the newest releases?” Yes, the newest front end: adds a caveat to GDC in GCC 15 or 16. DMD and LDC 1.43.0 are on 2.113.0, while GCC 15 implements 2.111.0 and GCC 16 a beta of 2.113.0: check which GCC your system packages.

In short: LDC on a Mac with Apple silicon and wherever the program’s speed matters most, DMD on x86-64 for the fastest builds while you edit, and GDC where your Linux distribution packages it and the front end of its GCC, 2.111.0 in GCC 15 or a 2.113.0 beta in GCC 16, is new enough.

Checkpoint

Check: DMD, LDC and GDC and their front end

  1. Question 1 of 3 You build on a Mac with Apple silicon and want the newest front end. Which compiler of this lesson fits?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: LDC 1.43.0

    The machine rules out DMD, which the download page offers for i386 and amd64, and GDC, which comes with GCC on Linux. LDC 1.43.0 has a package for Apple silicon and the 2.113.0 front end.

  2. Question 2 of 3 What did ldmd2 -vdmd show about ldmd2?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: It takes DMD's options and runs ldc2 with LDC's matching ones

    ldmd2 lets a build script written for DMD's options run with LDC. With -vdmd it prints the ldc2 command it runs, which showed -inline turned into -enable-inlining.

  3. Question 3 of 3 Since which GCC release has GCC included the D language?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: GCC 9

    GCC 9 brought D into GCC as GDC, at 2.076; GCC 15 implements 2.111.0 and GCC 16 a beta of 2.113.0. So a Linux distribution's GCC packages can carry gdc, at the front end of the GCC release they hold.

Phobos is grouped by what you want to do

wordfreq imports four modules, and each comes from a different group of Phobos: D's standard library: the modules of the std package, such as std.stdio, std.algorithm and std.csv. It comes with every D compiler, at the same version as the front end, and is built on druntime., D’s standard library: std.stdio for writeln, std.string for toLower and split, std.array for join, and std.algorithm for sort. The Phobos index groups its modules by task, and a module may appear in more than one group:

Group on the index Modules in it Used in this mini-course for
Algorithms and ranges std.algorithm, std.range sorting and lazy pipelines
Array manipulation std.array, std.algorithm join, arrays from ranges
String manipulation std.string, std.uni, std.format case, splitting, formatting
Data formats std.csv, std.json the CSV tool of the project
Type manipulations std.conv, std.sumtype text to numbers with to
Metaprogramming std.traits, std.meta code that runs while compiling
I/O and file system std.stdio, std.file printing, reading files
Interoperability core.stdc.stdio, core.stdc.string calling the C library

The last row is not Phobos at all. Its modules start with core, the package of druntime: The D runtime library: the object module that every program imports, the garbage collector, the code that starts a program and calls main, and the core package, whose core.stdc modules declare the C library., the D runtime, which holds the garbage collector, the code that starts a program and calls main, and core.stdc, D’s declarations of the C library. The module object belongs to it too, and every program imports it without an import line. Phobos is built on druntime, and druntime calls the C library:

A D program imports Phobos modules, Phobos is built on druntime, and druntime calls the C library and the system.Your programwordfreq's source/app.dPhobos: std.*std.stdio, std.string,std.algorithm, std.arraydruntime: core.*, objectGC, program start-up,core.stdc bindingsThe C libraryand the systemimportsbuilt oncalls

What a D program builds on

Text description of the diagram

The diagram is read from top to bottom, one layer per box.

  1. Your program, here the source/app.d of the wordfreq project, imports the modules it uses.
  2. Phobos is the standard library, the std package: std.stdio for input and output, std.string and std.array for text and arrays, std.algorithm for sorting and searching, and many more.
  3. Phobos is built on druntime, the D runtime: the object module that every program imports automatically, the garbage collector, the code that starts a program and runs its main, and core.stdc, D's declarations of the C standard library.
  4. druntime calls the C library and, through it, the operating system.

When you need something, look for the task on the index first, because a module’s name rarely tells everything it holds. std.algorithm alone covers comparing, iterating, changing, searching, set operations and sorting, in the submodules std.algorithm.comparison, .iteration, .mutation, .searching, .setops and .sorting.

Checkpoint

Check: Finding a module in Phobos and druntime

  1. Question 1 of 2 wordfreq sorts its words with sort. In which module of Phobos is sort, and under which submodule?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: std.algorithm, in std.algorithm.sorting

    The Phobos index groups modules by task: sorting is under algorithms and ranges, in std.algorithm, whose submodules are comparison, iteration, mutation, searching, setops and sorting.

  2. Question 2 of 2 Which of these belong to druntime rather than to Phobos?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • object
    • core.stdc.stdio

    Phobos is the std package. druntime, the D runtime it is built on, holds object, the garbage collector, the code that starts a program and the core package, whose core.stdc modules call the C library.

Where D is used, and how to read that list

dlang.org keeps a page of organisations using D: 32 entries, each with a logo and a few lines. Its last line asks organisations that are missing to create an issue or open a pull request on GitHub, so the entries show who chose to be listed. They do not show how many organisations or people use D, and a missing name proves nothing. Read by field, the 32 entries cover:

Field What the entries describe
Search and text marketing technology, SEO tools and search, language processing
Finance a credit exchange, asset management, a transaction network
Games and media game production, sound and signal processing, photo and video software
Engineering vehicle research, equipment testing, vessel design, weather services
Data and software data modelling and engineering, storage, licence management
Online services a marketplace, delivery, social media, travel, education

Two facts from this lesson complete the picture. GDC is part of GCC, with official packages for several Linux distributions, and the LDC 1.43.0 release adds packages for building programs for Windows, Android and WebAssembly from one machine. Whether D is worth learning for a job is a question about that job’s field and machines, and the list, read this way, answers part of it: which fields have used D in production, as the entries themselves report.

Checkpoint

Check: Where D is used, and what the list shows

  1. One question dlang.org's page of organisations using D has 32 entries. What does it show?

    Choose one answer.

    Show the answer: One question

    Answer: Which organisations chose to be listed, and the fields in which they report using D

    Read by field, the 32 entries cover search and text, finance, games and media, engineering, data and software, and online services. The list tells you where D has been used in production, as its entries report it.

Practise: check a dub recipe

The exercise turns the recipes session into code: given a recipe, list what dub 1.42.0 reports about it, in dub’s order. The sample tests replay the five recipes of the session and the recipe of wordfreq.

Exercise · Easy · Python

Check a dub recipe the way dub 1.42.0 does

A recipe that dub refuses costs a build; one it only warns about builds a program under a name you did not mean. Write problems(recipe) in recipe.py. It takes a recipe as json.load reads dub.json, a dictionary, and returns the list of its problems in the order dub 1.42.0 reports them, as the lesson's session showed:

  1. No "name": only "error: the recipe has no name".
  2. A "targetType" that is not one of autodetect, none, executable, library, sourceLibrary, staticLibrary or dynamicLibrary (the case counts): only "error: unknown targetType <value>".
  3. Otherwise the name, a non-empty text: a name with an upper-case letter adds "warning: the name should be lower case"; a lower-case name with any character other than a to z, 0 to 9, - and _ adds "warning: the name may use only a-z, 0-9, - and _" instead. A name with a space then adds "error: the name contains a space", and nothing more is checked.
  4. Then the target type: none without a non-empty "dependencies" adds "error: targetType none needs dependencies", and sourceLibrary adds "error: a main package cannot be a sourceLibrary".

Then write builds(recipe): True when no problem starts with error:. A recipe without "targetType" is autodetect. The starter checks the name the obvious way; run the sample tests to see where it differs from dub.

Starter code · recipe.py

TARGET_TYPES = ("autodetect", "none", "executable", "library", "sourceLibrary", "staticLibrary", "dynamicLibrary")


def problems(recipe):
    """The problems dub 1.42.0 reports for a recipe, in its order."""
    found = []
    name = recipe.get("name", "")
    if not name:
        found.append("error: the recipe has no name")
    if any(c.isupper() for c in name):
        found.append("warning: the name should be lower case")
    if not all(c.isalnum() or c in "-_" for c in name):
        found.append("warning: the name may use only a-z, 0-9, - and _")
    if " " in name:
        found.append("error: the name contains a space")
    target = recipe.get("targetType", "autodetect")
    if target.lower() not in [t.lower() for t in TARGET_TYPES]:
        found.append(f"error: unknown targetType {target}")
    return found


def builds(recipe):
    """True when dub builds the recipe, warnings or not."""
    return not any(p.startswith("error:") for p in problems(recipe))
The sample tests · test_recipe.py
from recipe import builds, problems


def test_the_recipe_of_this_lesson():
    """wordfreq's dub.json has nothing to report"""
    recipe = {"name": "wordfreq", "description": "Counts how often each word occurs in its arguments.", "license": "BSL-1.0", "targetType": "executable"}
    assert problems(recipe) == []
    assert builds(recipe)


def test_an_upper_case_name_only_warns():
    """recipes/upper: dub warned and built the program WordFreq"""
    assert problems({"name": "WordFreq"}) == ["warning: the name should be lower case"]
    assert builds({"name": "WordFreq"})


def test_a_space_stops_the_build():
    """recipes/spaced: the character warning, then the error"""
    assert problems({"name": "word freq"}) == ["warning: the name may use only a-z, 0-9, - and _", "error: the name contains a space"]
    assert not builds({"name": "word freq"})


def test_an_unknown_target_type():
    """recipes/target: program is not a target type"""
    assert problems({"name": "wordfreq", "targetType": "program"}) == ["error: unknown targetType program"]


def test_no_name():
    """recipes/nameless: nothing else is checked"""
    assert problems({"description": "A project without a name"}) == ["error: the recipe has no name"]


def test_the_target_type_is_read_before_the_name():
    """recipes/both: only the targetType error"""
    assert problems({"name": "WordFreq", "targetType": "program"}) == ["error: unknown targetType program"]


def test_upper_case_hides_the_character_warning():
    """WORD-freq.x has a dot too, yet gets one warning"""
    assert problems({"name": "WORD-freq.x"}) == ["warning: the name should be lower case"]


def test_letters_outside_a_to_z():
    """ö is a letter, but not one of a to z"""
    assert problems({"name": "wörd"}) == ["warning: the name may use only a-z, 0-9, - and _"]
    assert builds({"name": "wörd"})


def test_target_types_with_rules():
    """none needs dependencies; a main package is never a sourceLibrary"""
    assert problems({"name": "wordfreq", "targetType": "none"}) == ["error: targetType none needs dependencies"]
    assert problems({"name": "wordfreq", "targetType": "none", "dependencies": {"argsd": "~>1.0"}}) == []
    assert problems({"name": "w.f", "targetType": "sourceLibrary"}) == ["warning: the name may use only a-z, 0-9, - and _", "error: a main package cannot be a sourceLibrary"]
    assert problems({"name": "word_freq-2", "targetType": "dynamicLibrary"}) == []


def test_the_case_of_a_target_type_counts():
    """Executable with a capital E is not executable"""
    assert problems({"name": "wordfreq", "targetType": "Executable"}) == ["error: unknown targetType Executable"]
A hint

Return as soon as rule 1 or rule 2 applies: dub stops reading the recipe there. For rule 3, use if for the upper-case check and elif for the character check, since an upper-case name never gets the second warning.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then the self-check, on five ideas from ldc2 --version to the list of organisations. It begins where your answers are weakest, and every answer is explained right away:

Practice that adapts to you

Practise this lesson

12 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 Why compare the SHA-256 of the downloaded archive with ldc2-1.43.0.sha256sums.txt before you unpack it?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: To be sure it is the published file, not a damaged or another one

    The release lists a SHA-256 for every archive in one file. shasum -a 256 on your download must give the same value, which for the macOS arm64 archive starts 5e332349; otherwise download it again.

  2. Question 2 of 12 The default build of hello.d was 1,188,488 bytes and the -O build 1,183,896 bytes. How many bytes did -O save?

    Type a number.

    A hint

    Subtract the two sizes that wc -c printed.

    Show the answer: Question 2 of 12

    Answer: 4592 bytes

    1,188,488 − 1,183,896 = 4,592 bytes, less than 0.4 %. hello.o, the compiled program itself, was only 24,808 bytes: nearly everything else is druntime and Phobos, which -O does not shrink.

  3. Question 3 of 12 A classmate's hello.d prints "Front end version: 2112" and their output of a later example differs from the page. What do you check first?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Which compiler is first on their PATH, with ldc2 --version, because 2112 is not the pinned front end

    The version comes first: __VERSION__ printed 2113 with the pinned LDC. 2112 means another compiler or another release came first on the PATH, so fix that before comparing outputs.

  4. Question 4 of 12 On a new Mac, ldc2 checks and compiles hello.d, but the build stops at the link because no C compiler is found. What do you install?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: Apple's Command Line Tools, with xcode-select --install

    The LDC package needs no installation of its own, but it links through a C compiler such as clang or gcc on the PATH. On a Mac, xcode-select --install installs the Command Line Tools, clang included.

  5. Question 5 of 12 Which settings does dub's release build type turn on, by dub's reference?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: Release mode, optimisation and inlining

    dub build --build=release builds the release type: release mode, optimisation and inlining. debug is the default, and unittest is what dub test builds.

  6. Question 6 of 12 A teammate names a new package "csv tools" in dub.json and wants the program to be called csv tools. What do you suggest?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: Name the package csv-tools, and add "targetName" if the program must keep another name

    Package names use lower-case letters, digits, - and _. dub warned about WordFreq and built it, aborted on a name with a space, and its warning points to "targetName" for the program's file name.

  7. Question 7 of 12 Which part of LDC 1.43.0 turns the checked program into machine code?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: LLVM 22.1.8, its back end

    LDC is DMD's front end with LLVM behind it, GDC is the same front end with GCC behind it, and DMD has a back end of its own.

  8. Question 8 of 12 Your code uses a feature added to the language in 2.112. Which of these compilers cannot build it?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: GDC from GCC 15

    DMD and LDC 1.43.0 are on 2.113.0, GCC 16's GDC on a beta of 2.113.0 and GCC 15's GDC on 2.111.0, so of these only the GDC of GCC 15 cannot build a 2.112 feature.

  9. Question 9 of 12 You need to read a CSV file in D. Where on the Phobos index do you look?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: Data formats, where std.csv is

    The index groups modules by task, so a file format is under data formats: std.csv is the one the project lesson of this mini-course uses.

  10. Question 10 of 12 Which module does every D program import without an import line?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: object

    Only object, from druntime, comes without asking; every Phobos module, std.stdio included, needs an import.

  11. Question 11 of 12 Which question can dlang.org's list of organisations using D answer?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: Has an organisation in my field reported using D in production?

    The list is a set of self-chosen entries with what each organisation does: a way to see the fields where D has been used, not a measure of how many use it.

  12. Question 12 of 12 Where does the GDC project say you get GDC?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: From GCC distributors, with official packages for several Linux distributions

    GDC is part of GCC, so it comes the way GCC comes: from your Linux distribution's packages, searched for as gdc or gcc-d.

Interview questions

Warm-up (fresher to mid-level): What do DMD, LDC and GDC share, and how do you choose one?

They share the front end, the part of the compiler that reads D source and checks it, and with it the language version: each DMD release numbers the language, druntime and Phobos together, and LDC and GDC put another back end behind that front end. DMD has its own back end, which its download page calls very fast to compile with; LDC uses LLVM and GDC uses GCC, and the page credits both with strong optimisation. The choice follows the machine and the need: only LDC lists a package for a Mac with Apple silicon, so it is the default there; on x86-64, DMD suits the edit-and-test loop and LDC or GDC the release build. Check the front-end version too: LDC 1.43.0 is on 2.113.0, and GDC on that of its GCC release, 2.111.0 in GCC 15 and a beta of 2.113.0 in GCC 16.

Two longer questions, on a test suite that passes without testing anything and on compilers for a team:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to senior

    A D tool's pipeline runs dub test on every push and has been green for months. Then a bug ships in a function whose unittest block, it turns out, expects the wrong value. How can both be true, and what do you change?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Your team writes a D tool on Macs with Apple silicon and ships it for Linux servers on x86-64. Which compilers do you use where, and how do you keep the developers' builds and the shipped build from drifting apart?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which three versions does ldc2 --version report?

    Three: LDC 1.43.0 itself, the pin; the front end of DMD v2.113.0, with which it reads D; and LLVM 22.1.8, which makes the machine code. A program's __VERSION__ reports 2113.

  2. What does ldc2 -run hello.d leave in the folder?

    Nothing new: it compiles, links and runs the program in one step and then deletes it. A plain ldc2 hello.d leaves hello.o and a 1,188,488-byte program, mostly druntime and Phobos.

  3. A wrong unittest sits in source/app.d. Why does dub test still report success?

    dub's test build brings its own main and leaves out the file holding yours, unittest blocks and all. Moved into a module, the same test failed with an AssertError.

  4. What are dub's debug and release build types?

    debug is the default. release, chosen with dub build --build=release, means release mode with optimisation and inlining. dub test builds the unittest type and runs the tests.

  5. What do DMD, LDC and GDC have in common, and where do they differ?

    They share the front end that reads and checks D, and with it the language version. The back end differs: DMD's own, LLVM behind LDC and GCC behind GDC.

  6. Which D compiler suits a Mac with Apple silicon, and why?

    LDC: DMD's download page offers builds for i386 and amd64 only. On x86-64, DMD suits quick builds while editing and LDC or GDC the optimised release.

  7. What is ldmd2 for?

    It accepts DMD's options and runs ldc2 with LDC's, so a build script written for DMD works with LDC; with -vdmd it shows that -inline turned into -enable-inlining.

  8. What is druntime, and how does it relate to Phobos?

    The D runtime: the garbage collector, the start-up code that calls main, and core.stdc, D's declarations of the C library. Phobos, the standard library, is built on top of it.

Key takeaways

  • LDC 1.43.0 installs from one release archive into a folder you own, and ldc2 --version names LDC 1.43.0, the DMD v2.113.0 front end and LLVM 22.1.8; __VERSION__ gives 2113.
  • ldc2 -run builds and runs a file in one step, and a normal build of hello.d was 1,188,488 bytes, nearly all of it druntime and Phobos.
  • dub builds a package from its recipe and its source folder; dub test leaves out the file with main, so code and its unittest blocks belong in modules of their own.
  • DMD, LDC and GDC share one front end and differ in their back ends: LDC for Apple silicon and fast programs, DMD for fast builds on x86-64, GDC from a Linux distribution’s packages, at the front end of its GCC release.
  • Phobos is grouped by task on its index and is built on druntime, whose core package also declares the C library.
  • The list of organisations using D shows the fields where its entries report production use, not how many use D.

Terms in this lesson

build type (dub)
A named set of compiler settings that dub applies to a build: debug, the default, keeps debug information; release turns on release mode, optimisation and inlining; unittest compiles the unittest blocks in.
druntime
The D runtime library: the object module that every program imports, the garbage collector, the code that starts a program and calls main, and the core package, whose core.stdc modules declare the C library.
dub recipe
The file that describes a dub package, dub.json or dub.sdl: its name, description, licence, target type and dependencies. dub reads it before every build and refuses one it cannot accept, such as a name with a space.
front end (D compiler)
The part of a D compiler that reads the source, checks it and works out what it means, before a back end turns it into machine code. DMD, LDC and GDC share DMD's front end, and __VERSION__ gives its version as a number: 2113 for 2.113.
Phobos
D's standard library: the modules of the std package, such as std.stdio, std.algorithm and std.csv. It comes with every D compiler, at the same version as the front end, and is built on druntime.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.