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.
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.
One ldc2 command compiles, links and runs a file
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:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- 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.
- 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.
- The hash differs. Download the archive again and compare once more. Unpack nothing whose hash does not match the list.
- 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.
- 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.
- 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.
- 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.
- 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.
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:
// 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);
} 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
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:
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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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.
- 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:
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:
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
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:
- 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.
- 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++.
- 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.
- 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.
- 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.
- 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:
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:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| 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
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:
What a D program builds on
Text description of the diagram
The diagram is read from top to bottom, one layer per box.
- Your program, here the source/app.d of the wordfreq project, imports the modules it uses.
- 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.
- 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.
- 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
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
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:
- No
"name": only"error: the recipe has no name". - A
"targetType"that is not one ofautodetect,none,executable,library,sourceLibrary,staticLibraryordynamicLibrary(the case counts): only"error: unknown targetType <value>". - 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 thanatoz,0to9,-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. - Then the target type:
nonewithout a non-empty"dependencies"adds"error: targetType none needs dependencies", andsourceLibraryadds"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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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.
-
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?
-
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?
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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 --versionnames LDC 1.43.0, the DMD v2.113.0 front end and LLVM 22.1.8;__VERSION__gives 2113. ldc2 -runbuilds 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 testleaves out the file withmain, 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
- LDC 1.43.0 release notes and packages (LDC developers)
- README of the LDC binary packages (LDC developers)
- Installing the command-line tools (Apple)
- Downloads: DMD, GDC and LDC (D Language Foundation)
- D change log: all releases (D Language Foundation)
- D change log 2.113.0 (D Language Foundation)
- D Language Specification: Lexical, special tokens (D Language Foundation)
- DUB documentation: First steps (DUB developers)
- DUB reference: build types (DUB developers)
- DUB reference: build settings (targetType, sourcePaths, mainSourceFile) (DUB developers)
- Phobos runtime library: index of modules (D Language Foundation)
- std.algorithm (D Language Foundation)
- Organizations using the D Language (D Language Foundation)
- The GNU D Compiler (GDC) manual (GCC project)
- GCC 9 release series: changes (D added to GCC) (GCC project)
- GCC 15 release series: changes (D) (GCC project)
- GCC 16.1.0 sources: gcc/d/dmd/VERSION, the D front end in GDC (GCC project)
- GCC releases (GCC project)
- GDC downloads (GDC project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress