D (2.112)
This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].
1 Assessment
This page assesses D in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.
Core coupling 57/100 · Ecosystem coupling 84/100 · Engineering readiness 65/100
1.1 Core coupling
1.1.1 Type
- ✅ Static typing with inference:
auto, template argument deduction, andtypeofinfer most declarations, so the type is fixed at compile time without being written at every site - ✅ Templates avoid the angle-bracket problem: Instantiation uses the
!syntax (foo!int,foo!(int, string)) withif (isInputRange!R)constraints, so generic code carries none of C++’s nested<>noise - ✅ Modules are the compilation boundary: A module is declared with
modulein one source file and consumed withimport, so there are no headers and no include guards tying a declaration to a textual include order - ✅ UFCS decouples call syntax from the definition site:
x.f(y)is rewritten tof(x, y), so a free function composes into a method-call chain without being a member of the type - ✅
immutableandconstare transitive: A qualifier reaches through pointers and members, so a read-only view is stated once rather than per access - ⚠️ Operator overloading is not visible in cost:
opBinary,opCmp,opIndex, andopAssignlet a type redefine+,<,[], and=, so a dense expression can hide work the call site does not show (T8)
1.1.2 Construction
- ✅ Structs are constructed in place:
structis a value type, so a local, a field, or an array element is constructed where it is declared without a heap allocation - ✅ Contract programming separates the specification from the body:
in,out, andinvariantblocks state preconditions, postconditions, and class invariants as separate declarations, so a check is not buried in the implementation - ✅
scopeguards attach cleanup to the construction site:scope(exit),scope(success), andscope(failure)run at block exit beside the code that acquired the resource - ✅
@disable this()forbids an illegal construction: A type can delete a constructor, so an object cannot be built by callingnew - ⚠️ Mutability is the default: A variable is mutable unless it is declared
immutableorconst, so immutability is opt-in rather than the default reading of a declaration (C2)
1.1.3 Memory
- ❌ Garbage collection pauses are non-deterministic: The default
newpath allocates on the collector’s heap, and a collection runs on the collector’s schedule rather than the program’s (M2) - ⚠️ RAII is partial: Struct destructors (
~this) andscope(exit)release a value type deterministically, but aclassinstance is collected, so its destructor has no defined run time (M3) - ❌ No ownership or lifetime checking: The compiler does not track which reference owns an allocation, so a dangling pointer or a double release in
@systemcode is not diagnosed (M4) - ✅
@nogcremoves the collector from a call tree: A@nogcfunction may not allocate through the GC, so its pauses are excluded at compile time - ✅
-betterCexcludes the runtime entirely: The program links without druntime or the collector, which is what the embedded and no-GC case needs - ✅
destroyreleases a value at a chosen point:destroy(x)runs the destructor and resets the value, giving an explicit release where the collector would otherwise decide
1.1.4 Error
- ❌ Unchecked exceptions are the only error channel:
throwneeds no declaration in the signature and the compiler does not check that a handler exists;nothrowis an opt-in promise (Er2) - ✅
nothrownames the functions that cannot fail: Anothrowfunction may not throw, so the property is checked at the declaration - ✅
scope(failure)separates the recovery path from the normal path: Cleanup written there runs only when the block leaves by an exception - ✅
try/catch/finallyis the standard shape: Acatchclause names the type it handles, andfinallyruns on every exit - ⚠️ Contracts and assertions disappear under
-release:in,out, andassertare compiled out in a release build, so their guarantee is a debug-time one
1.1.5 Concurrency
- ✅ The
sharedqualifier makes cross-thread state visible: Data declaredsharedcannot be read or written through an ordinary reference, so the cast that crosses a thread boundary is the place the synchronisation is declared - ✅
std.concurrencygives message passing:spawnstarts a thread with its own message queue, andsend/receivepass values between threads without a shared lock - ✅
std.parallelismgives structured parallel loops:parallelandtaskPoolmap a loop body over a range, so the common data-parallel case is stated rather than hand-threaded - ✅ Atomic primitives are in the standard library:
core.atomicprovidesatomicOp,cas, andatomicLoad, so a lock-free update has a named primitive - ❌ Synchronisation is manual and unchecked:
synchronized,Mutex, andcore.atomicare the only tools, and the compiler does not prove the locking discipline or detect a race (K3)
1.1.6 Platform
- ✅ Three independent compilers target the same language:
dmduses its own Digital Mars backend,ldcuses LLVM, andgdcuses the GCC backend, so a program is not bound to one vendor’s toolchain - ✅ Native code is produced for many targets: Linux, Windows, macOS, and the BSDs on x86, x86-64, ARM, and AArch64 are supported targets
- ✅ C and C++ interoperability is a language feature:
extern(C),extern(C++), and theimportCcompiler feature let a D module call existing C and C++ binaries without a binding generator - ✅
-betterCremoves the runtime dependency: A BetterC program links like a C program, without druntime or a GC - ✅ The C preprocessor is not part of the language: There is no textual include and no macro layer above the module system
- ⚠️ The three compilers advance at different rates:
ldcandgdcfollowdmd’s front end with a lag, so a version-specific feature may not be available on every compiler at once
1.1.7 Trade-offs (not coupling)
- Better C++ is a target, not a guarantee: Porting C++ to D is a rewrite with its own cost, and the two languages interoperate at the ABI rather than at the source
- GC or manual memory is a per-program choice:
-betterCand@nogcbuy determinism at the cost of the collector’s convenience - Compile-time execution is a deliberate shift of work: CTFE,
static if, and string mixins move computation to the build, so it is not a runtime mechanism at all - Multiple paradigms widen the surface: Imperative, object-oriented, functional, and metaprogramming styles are all supported, and the choice is the programmer’s
- Niche adoption is a market fact, not a language defect: The smaller library pool follows from adoption, not from a construction of the language
1.2 Ecosystem coupling
1.2.1 Performance
- ✅ Native compilation with no interpreter:
dmd,ldc, andgdcemit machine code, so there is no bytecode dispatch or JIT warm-up between the source and the CPU - ✅
ldcandgdcreuse mature optimisers: The LLVM and GCC backends apply the optimisation pipeline that compiles C and C++ - ✅ CTFE moves work to compile time:
static if,mixin, and template instantiation are evaluated by the compiler, so the runtime pays nothing for them - ✅
@nogcbounds the runtime cost of a call tree: A function that cannot allocate has no collector interaction at all - ⚠️ The collector remains a latency risk: Throughput is good, but a collection can still lengthen a pause in code that allocates, so a latency-sensitive path needs
@nogc(R2)
1.2.2 Ecosystem
- ❌ The library pool is small:
dub’s package registry is far smaller than crates.io, npm, or Maven, so a project is more likely to write a library than to find one (Ec3) - ✅
dubis a standard package manager and build tool:dub.jsonordub.sdldeclares dependencies and targets, anddub build,dub run, anddub testcover the ordinary workflow - ✅ The C and C++ ecosystems are reachable:
extern(C),extern(C++), andimportCreduce the need for a D-specific binding - ✅
vibe.dsupports web services: An asynchronous I/O framework covers HTTP and socket work on the D runtime - ⚠️ Corporate backing is weak: The language is carried by its contributors rather than by a large vendor, so long-term support is the community’s
1.2.3 Evolution
- ✅ A deprecation cycle precedes a removal: A feature is deprecated for a release series before it is removed, and the changelog records both steps
- ✅
-previewflags gate changes to the front end: A new language behaviour ships behind a flag first, so a change can be tested before it becomes the default - ✅ The DIP process documents proposals publicly: The D Improvement Proposal repository holds the rationale for a language change
- ⚠️ Removals do happen: The changelog records deprecated features that were later dropped, so older source can need an edit at a major front-end step
- ⚠️ Front-end versions differ across the three compilers: A change fixed in
dmdreachesldcandgdclater, so a project can be pinned to one compiler for a period
1.2.4 Domain
- ✅ Systems and performance work is the design target: Native code, value types, and direct C interop are the native idiom
- ✅ Game development is a stated fit: The GC can be excluded, structs stay in place, and templates specialise per case, which is what a game loop needs
- ✅ Embedded and no-runtime work is supported by
-betterC: The program links without druntime, so it fits a bare-metal or constrained target - ✅ Web services are supported by
vibe.d: The framework covers asynchronous HTTP on the same runtime - ⚠️ The collector is the wrong model for hard real-time: A latency bound requires
@nogcor BetterC throughout the allocating path - ⚠️ D is rarely the default choice in any of these domains: The fits are real, but the surrounding library and hiring pool is smaller
1.2.5 Learning
- ✅ Moderate for an experienced C++ developer: Templates, RAII-style cleanup, and value types have direct C++ analogues
- ✅ Templates are easier to read than C++ templates: The
!instantiation syntax andifconstraints remove the ambiguity of nested angle brackets - ✅ Contract programming is a small notation:
in,out, andinvariantare a few keywords over the ordinary function body - ⚠️ The metaprogramming surface is large: Templates,
static if, CTFE, traits, and string mixins each have their own rules - ⚠️ The memory model is two models at once: A developer must know which allocations are collected and which are not, and
@nogcmakes the distinction a compile-time obligation - ⚠️ Four paradigms are available at once: Imperative, object-oriented, functional, and metaprogramming styles widen the surface a newcomer must choose from
1.2.6 Syntax
- ✅ Cleaner than C++ for similar expressive power: A module is one file, there is no header text, and there is no preprocessor
- ✅
!makes template instantiation unambiguous:foo!intcannot be confused with a comparison, which is C++’s parsing problem - ✅
autoremoves a declaration without hiding the type: The type is inferred and still checked at compile time - ✅
scope(exit)is a compact cleanup notation: One line states the release that other languages spell as atry/finally - ⚠️ Attributes accumulate on a signature:
@safe @nogc @property nothrow pureand their variants stack up, and the combination is part of the contract - ⚠️ String mixins hide the generated code:
mixin("...")inserts source the reader cannot see in the file’s order
1.2.7 Testing
- ✅
unittestblocks are part of the language: Aunittest { ... }block sits beside the code it tests, and it is compiled in when the-unittestflag is passed - ✅
dub testruns the blocks: The package manager builds and runs the test configuration - ✅ Contracts are executable invariants:
in,out, andinvariantchecks fail with a trace when a precondition is broken - ✅ Coverage is built in:
dmd -covwrites a per-line coverage report from a normal run - ⚠️ Tests are compiled only with
-unittest: A release build carries no test code, so the test set is a build configuration rather than a language guarantee - ⚠️ The test library is third-party: Fixture, mocking, and property-test helpers come from packages such as
unit-threadedrather than from the standard library
1.2.8 Security
- ✅
@safeis checked by the compiler:@safecode may not do pointer arithmetic, lose a type through a cast, or call@systemcode, so the unsafe operations are the ones that were declared - ✅ Bounds checking is kept where it is required: An array or a slice carries its length, and
@safecode retains the index check - ✅ Variables are initialised by default: A variable starts at its
.initvalue, so the uninitialised-read class of bug does not arise as it does in C - ⚠️
@trustedandcastare the escape hatch: A@trustedfunction or a pointer cast re-opens the unsafe surface, and its correctness is the author’s claim - ⚠️ The collector and the runtime are part of the attack surface: A bug in druntime or the collector is reachable from a
@safeprogram - ⚠️
-releaseremoves the contract checks: Assertions and contracts are compiled out, so a release build relies on the checks that@safekeeps
1.3 Engineering readiness
1.3.1 Reproducibility
- ❌ Builds are not byte-reproducible by default.
- ❌ Dependencies resolve without a lock by default.
1.3.2 Supply chain
- ⚠️ Published packages carry no signed provenance by default.
- ⚠️ The package registry is a flat name space.
1.3.3 Observability
- ⚠️ No stable tracing and metrics standard.
1.3.4 Feedback
- ✅ No charged mechanism in this dimension.
1.3.5 Interop
- ✅ No charged mechanism in this dimension.
1.3.6 Governance
- ✅ No charged mechanism in this dimension.
1.4 Profile
Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.
1.4.1 Energy and cost
What this axis asks: what does a running program cost in energy and time?
1.4.2 AI and agent readiness
What this axis asks: how well does a coding model and an agent work in this language?
- ❌ Sparse training data: models write it unreliably [8].
1.4.3 Safety certification
What this axis asks: is there a certified or standards-qualified path to a safety-critical system?
- ⚠️ No certified or standards-qualified path for safety-critical systems.
1.5 Scoring detail
1.5.1 Core coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Type | T1 |
dynamic no static check | — | 0 |
T2 |
weak static | — | 0 | |
T3 |
dynamic/duck dispatch | — | 0 | |
T4 |
no sum types | — | 0 | |
T5 |
no generics | — | 0 | |
T6 |
no namespaces | — | 0 | |
T7 |
type erasure/reflection | — | 0 | |
T8 |
operator overloading | ✅ | −4 | |
T9 |
inheritance required for polymorphism | — | 0 | |
T10 |
no null-safety | — | 0 | |
T11 |
no module boundary | — | 0 | |
T12 |
no effect tracking | — | 0 | |
T13 |
async/sync colouring | — | 0 | |
T14 |
unchecked foreign boundary | ✅ | −4 | |
| Type subtotal | −8 | |||
| Construction | C1 |
global mutable state by default | — | 0 |
C2 |
no immutability by default | ✅ | −4 | |
C3 |
constructors coupled to allocation | — | 0 | |
C4 |
no constructor/validation | — | 0 | |
| Construction subtotal | −4 | |||
| Memory | M1 |
manual deallocation | — | 0 |
M2 |
GC non-deterministic pauses | ✅ | −8 | |
M3 |
no RAII | ✅ | −4 | |
M4 |
no ownership model | ✅ | −8 | |
| Memory subtotal | −15 (−20) | |||
| Error | Er1 |
no structured path | — | 0 |
Er2 |
unchecked exceptions only | ✅ | −8 | |
Er3 |
errors ignored/non-exhaustive | — | 0 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | −8 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | — | 0 | |
K3 |
manual sync no race detection | ✅ | −8 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | −8 | |||
| Platform | P1 |
cannot function outside one vendor/domain | — | 0 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | 0 |
Core Dimensions score: 57/100 — total cost = −43 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.2 Ecosystem coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Performance | R1 |
interpreted no AOT/JIT | — | 0 |
R2 |
GC pauses | ✅ | −8 | |
R3 |
JIT warmup | — | 0 | |
| Performance subtotal | −8 | |||
| Ecosystem | Ec1 |
no package manager | — | 0 |
Ec2 |
fragmented tooling | — | 0 | |
Ec3 |
small/niche/discontinued | ✅ | −8 | |
Ec4 |
proprietary | — | 0 | |
Ec5 |
platform-locked tooling | — | 0 | |
Ec6 |
monoculture | — | 0 | |
Ec7 |
legacy burden | — | 0 | |
| Ecosystem subtotal | −8 | |||
| Evolution | V1 |
breaking changes | — | 0 |
V2 |
frozen | — | 0 | |
V3 |
discontinued | — | 0 | |
V4 |
glacial standards | — | 0 | |
| Evolution subtotal | 0 | |||
| Domain | D1 |
domain/vendor lock-in | — | 0 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | 0 | |||
| Learning | L1 |
steep | — | 0 |
L2 |
large surface/multi-paradigm | — | 0 | |
L3 |
sparse docs | — | 0 | |
| Learning subtotal | 0 | |||
| Syntax | S1 |
verbose | — | 0 |
S2 |
inconsistent | — | 0 | |
S3 |
punctuation-heavy | — | 0 | |
| Syntax subtotal | 0 | |||
| Testing | G1 |
weak tooling | — | 0 |
G2 |
poor debugging | — | 0 | |
G3 |
runtime-only errors | — | 0 | |
| Testing subtotal | 0 | |||
| Security | Q1 |
memory-unsafe | — | 0 |
Q2 |
dynamic/weak typing risk | — | 0 | |
Q3 |
known vulnerability/injection | — | 0 | |
| Security subtotal | 0 |
Meta Dimensions score: 84/100 — total cost = −16 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.3 Engineering readiness Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Reproducibility | Y1 |
build not reproducible | ✅ | −8 |
Y2 |
dependencies not pinned | ✅ | −8 | |
| Reproducibility subtotal | −15 (−16) | |||
| Supply chain | Y3 |
no signed provenance | ✅ | −8 |
Y4 |
registry without namespacing | ✅ | −4 | |
| Supply chain subtotal | −12 | |||
| Observability | Y5 |
no stable tracing standard | ✅ | −8 |
Y6 |
no structured logging | — | 0 | |
| Observability subtotal | −8 | |||
| Feedback | Y7 |
slow build | — | 0 |
Y8 |
no fast in-editor analysis | — | 0 | |
| Feedback subtotal | 0 | |||
| Interop | Y9 |
no foreign-function interface | — | 0 |
Y10 |
no portable target | — | 0 | |
| Interop subtotal | 0 | |||
| Governance | Y11 |
single-vendor governance | — | 0 |
Y12 |
closed specification | — | 0 | |
| Governance subtotal | 0 |
Readiness Dimensions score: 65/100 — total cost = −35 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: D’s design yields moderate language-imposed coupling for a language of its reach. The module system replaces headers, UFCS separates call syntax from definition, and contract blocks separate the specification from the body. Predicates such as @safe, @nogc, and nothrow turn several memory and error properties into declared and checked facts. Main strengths: compile-time execution removes metaprogramming from the runtime, three independent compilers keep the toolchain from locking in, and -betterC reaches the embedded case. Main weaknesses: unchecked exceptions are the only error channel, the collector’s pauses are not deterministic, no ownership model detects aliasing, and the ecosystem stays niche with weak corporate backing.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193