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, and typeof infer 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)) with if (isInputRange!R) constraints, so generic code carries none of C++’s nested <> noise
  • ✅ Modules are the compilation boundary: A module is declared with module in one source file and consumed with import, 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 to f(x, y), so a free function composes into a method-call chain without being a member of the type
  • ✅ immutable and const are 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, and opAssign let 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: struct is 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, and invariant blocks state preconditions, postconditions, and class invariants as separate declarations, so a check is not buried in the implementation
  • ✅ scope guards attach cleanup to the construction site: scope(exit), scope(success), and scope(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 calling new
  • ⚠️ Mutability is the default: A variable is mutable unless it is declared immutable or const, 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 new path 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) and scope(exit) release a value type deterministically, but a class instance 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 @system code is not diagnosed (M4)
  • ✅ @nogc removes the collector from a call tree: A @nogc function may not allocate through the GC, so its pauses are excluded at compile time
  • ✅ -betterC excludes the runtime entirely: The program links without druntime or the collector, which is what the embedded and no-GC case needs
  • ✅ destroy releases 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: throw needs no declaration in the signature and the compiler does not check that a handler exists; nothrow is an opt-in promise (Er2)
  • ✅ nothrow names the functions that cannot fail: A nothrow function 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/finally is the standard shape: A catch clause names the type it handles, and finally runs on every exit
  • ⚠️ Contracts and assertions disappear under -release: in, out, and assert are compiled out in a release build, so their guarantee is a debug-time one

1.1.5 Concurrency

  • ✅ The shared qualifier makes cross-thread state visible: Data declared shared cannot be read or written through an ordinary reference, so the cast that crosses a thread boundary is the place the synchronisation is declared
  • ✅ std.concurrency gives message passing: spawn starts a thread with its own message queue, and send/receive pass values between threads without a shared lock
  • ✅ std.parallelism gives structured parallel loops: parallel and taskPool map 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.atomic provides atomicOp, cas, and atomicLoad, so a lock-free update has a named primitive
  • ❌ Synchronisation is manual and unchecked: synchronized, Mutex, and core.atomic are 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: dmd uses its own Digital Mars backend, ldc uses LLVM, and gdc uses 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 the importC compiler feature let a D module call existing C and C++ binaries without a binding generator
  • ✅ -betterC removes 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: ldc and gdc follow dmd’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: -betterC and @nogc buy 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, and gdc emit machine code, so there is no bytecode dispatch or JIT warm-up between the source and the CPU
  • ✅ ldc and gdc reuse 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
  • ✅ @nogc bounds 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)
  • ✅ dub is a standard package manager and build tool: dub.json or dub.sdl declares dependencies and targets, and dub build, dub run, and dub test cover the ordinary workflow
  • ✅ The C and C++ ecosystems are reachable: extern(C), extern(C++), and importC reduce the need for a D-specific binding
  • ✅ vibe.d supports 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
  • ✅ -preview flags 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 dmd reaches ldc and gdc later, 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 @nogc or 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 and if constraints remove the ambiguity of nested angle brackets
  • ✅ Contract programming is a small notation: in, out, and invariant are 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 @nogc makes 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!int cannot be confused with a comparison, which is C++’s parsing problem
  • ✅ auto removes 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 a try/finally
  • ⚠️ Attributes accumulate on a signature: @safe @nogc @property nothrow pure and 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

  • ✅ unittest blocks are part of the language: A unittest { ... } block sits beside the code it tests, and it is compiled in when the -unittest flag is passed
  • ✅ dub test runs the blocks: The package manager builds and runs the test configuration
  • ✅ Contracts are executable invariants: in, out, and invariant checks fail with a trace when a precondition is broken
  • ✅ Coverage is built in: dmd -cov writes 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-threaded rather than from the standard library

1.2.8 Security

  • ✅ @safe is checked by the compiler: @safe code may not do pointer arithmetic, lose a type through a cast, or call @system code, 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 @safe code retains the index check
  • ✅ Variables are initialised by default: A variable starts at its .init value, so the uninitialised-read class of bug does not arise as it does in C
  • ⚠️ @trusted and cast are the escape hatch: A @trusted function 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 @safe program
  • ⚠️ -release removes the contract checks: Assertions and contracts are compiled out, so a release build relies on the checks that @safe keeps

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?

  • ✅ Compiles to native code: the fastest class in the language energy and time benchmarks [5], [6]. A causal re-analysis attributes the difference to execution time [7].

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:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
W. Bright and A. Alexandrescu, “The d language specification.” D Language Foundation, 2024. Available: https://dlang.org/spec/spec.html
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
R. Pereira et al., “Ranking programming languages by energy efficiency,” Science of Computer Programming, 2021, doi: 10.1016/j.scico.2021.102609.
[7]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[8]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.