dd86k's blog

Machine code enthusiast

D: After 10 Years

Author: dd
Published: 2026-03-06
Categories:

In mid-2016, I grew tired of the .NET Runtime and C#, and I was looking to jump to a new programming language, something machine-native for a change.

In 2016, these were my options:

  • Rust
    • Rust only has one compiler backend and didn’t seem very portable.
    • Rust is so incredibly verbose and rigid, doesn’t help with prototyping.
    • Package management via cargo is nice. Allowing in-source macros to invoke shells is weird, though.
    • Backed by Mozilla at least. 💰
  • Go
    • Go has a weird way of managing packages (in-source, pulled by the compiler).
    • Has a GC, which helps with prototyping, but seems to integral to the language: i.e., did not seem to allow the use of another implementation.
    • GC can be tweaked at runtime, which is nice.
    • Language is simple and easy to follow.
    • Backed by Google at least. 💰
  • D
    • D answers all Rust and Go issues personally.
    • DMD, GDC (GCC-based), and LDC (LLVM-based) available compilers.
    • D is a simple language and resembles C# a lot in writing.
    • Multiparadigm: Imperative first, OOP and functional on top.
    • Package management is done with DUB, a dedicated tool.
    • Only DUB packages run pre- and post-build commands (for I/O access), not source.
    • Official shebang support: Support for D as a scripting language.
    • GC can be hacked, replaced, tweaked at compile or runtime.
    • Native C, C++, and Objective-C interfacing. No SWIG here.
    • MSVC-flavoured inlined assembler (DMD).
    • Embedded documentation.
    • Backed by… No major company initially.

At least, that’s as much as I remember.

After briefly overviewing Rust and Go, I decided to pick the D programming language and the free Programming in D e-book.

Let’s look at some of the stuff that I actually published and what I’ve learned from them.

ddcpuid

Started in June 2016, this is the second ever project I initiated with D. It primarily made use of DMD’s x86 inline assembler.

The second project? Well, the first was a port of fwod. The source of the port is lost, I believe. Hey, stick to NetHack, will you?

Nonetheless, I started learning about the x86 CPUID instruction and flags, assembler flavours (GNU extended assembler with GDC), BetterC, naked scopes, ABIs, prologues and epilogues. Understanding the machine little by little, finally arranging instructions in such fine manner. Those instructions that I kept seeing on Visual Studio’s disassembler window, in such awe, now in my control.

Because of system requirements, and being tired of dealing with x86 feature flags, I ended the ddcpuid project in 2025.

dfile

Have you ever wanted a worse flavour of file.1? I did, and so started dfile in December 2016.

While the codebase still reeks of Amateur Hour (magic numbers, callees doing I/O, useless inline assembler, etc.), I still had fun with this one. Improving my skills with file I/O via both std.stdio.File and FILE*.

None of the code is pretty… But looks like I had some fun integrating Windows symlink functions. I believe this project made me learn how to sort of deal with OS specific functions.

dd86 (and ddx86)

In early January 2017, for my amazement of emulators, I started dd86… In C++. Then changed it to D on February 8th, 2017.

Yes, I know, odd decision to do that in-repo. Worse, I retried the emulator effort in 2020. (Don’t try either, they both fail to interpret ModR/M.)

I’ll be frank, both attempts were a complete failure on my end in an attempt to understand x86 and emulators. That’s okay, we all start somewhere. That was here. This sort of project, however, still allowed me to learn a little more about the PC architecture and how to structure my projects a little better.

Learned x86 better since, and to avoid other git-related crimes. They’re included here, because they are part of my history, even as failures. I’m not retrying this kind of thing, there are enough x86 emulators out there!

ddhx

I was looking to port my 0xdd utility, so in 2017, I initiated ddhx as a port of my “hexadecimal file viewer” with a newer, simpler interface.

There is already an entry about ddhx’s visual history.

While not immediately obvious, ddhx saw a million mistakes and corrections. From design to making releases. Learning how program flow is important and knowing how to glue various components can impact a project’s success.

Remember: Always start small. Prioritize your needs. If you make anything, make the cheapest prototype you can, and build on top of that. The topic of software architecture deserves its own article, for sure.

Thanks to that (and never giving up), version 0.9 was released with support for color customization. Something I’d never imagine myself including in something I make.

Alicedbg

Alicedbg saw its first commit in December 2019.

I have always wanted to make a debugger. This links back to debugging in Visual Studio again.

This project is made entirely with BetterC in mind, initially out of worry that the GC might clash with debug APIs and memory managed by it. So, why not? There were some cases where Phobos would not link properly (forgotten where), avoiding Phobos here helped with those edge cases (portability).

In any case, Alicedbg remains an active project, still in BetterC, with utilities and other high-level abstractions to help manage things. Templates and compile-time features still work great under BetterC.

ddgst

At the time, I wanted to verify my downloads, so I made my own hasher program in October 2020.

Learned how to create interfaces, structure templates, and saw to integrate SHA-3 with the Digest API in January 2021.

It was interesting to check compiler outputs on Godbolt and performing benchmarks. I believe I pulled a pretty decent result without resorting to SIMD (which D supports, but last I tried many years ago, it was only available on DMD and LDC?).

Aliceserver

I wanted to integrate Alicedbg with VSCode, so I started Aliceserver in February 2024.

A real test that puts my exprience into fruitition: OOP, multisession, I/O, multithreading.

D in 2026

While I’m perfectly happy to keep using D, and D is still trucking around, there is this sensation that I need to learn something else due to stagnation, enterprise support, portability…

Here are some nitpick issues I have noted with D:

  • Recent compilers, notably GDC and LDC, bundle the Druntime as a shared object by default. In theory, nice, we can just update the runtime for services, just like with most C runtimes! In practice: App still needs to be updated on ABI breaking change (stable distros in theory helps here), adds a runtime dependency, and makes it a little harder to make static builds (with “drop-in-place” solutions).
  • Class .ctor can’t init with a “static” variable that’s __gshared, for example, with dealing with a function that loads a dynamic library.
  • Function attributes cannot be conditional, e.g., export. Or at least, did not figure that out yet.
  • unittests with extern (C) will lead the linking errors if two unittests are at the same line in two modules. (e.g., __unittest_L153_C1 or __unittest_L153_C9 with @system) Solution is to mark unittests as extern (D) if you use extern (C): to mark the entire module.
  • scope(exit) will execute within version(...) scopes and not its parent scope, even if it is a function body.
  • An enum member called max overrides the .max attribute, which will lead to confusion. It should just be removed. Rely on EnumMembers!N.length instead.
  • By specification, integer operations are promoted to int (it didn’t use to!), even if the destination type is long/ulong. Example: ulong raw = (0x1234 << 22) | ((15 & 31) << 17) | ((23 & 31) << 12) | (336 & 4095); will return 0x8d1f_7150, but not 0x4_8d1f_7150. Solution here is to promote one constant to use the L suffix (0x1234L), but that’s still easy to miss. Behavior seen on all compilers, since they use DMD as a front-end.
  • Extern definitions are part of the function pointer, which makes calling functions with function pointers very annoying when they don’t match with the exact extern.
  • std.container.rbtree.RedBlackTree when using opSlide/lowerBounds/upperBounds, the .front() function gives values. Even when using foreach(ref ..., the element cannot be edited, and in anyway, will need to be re-inserted for the key (for cmp func) to be updated.
  • UFCS collision. Too many times I’ve had a function being called when trying to access a field of a structure or class instance. For example: text (std.conv) being called and causing confusion when trying to access a text member out of a structure instance.
  • Compilers are only officially available for Windows, macOS, Linux, and FreeBSD. x86 and Arm mostly. OpenBSD egdc port is nice. Lacks a prebuild option for NetBSD. (I tried porting GDC and failed)

There are likely more, I only started noting these some few years ago.

OpenD

Adam, after being frustrated with current efforts, started OpenD, a soft fork of DMD in hopes to improve the stagnation of D.

There are some points I do like:

  • Naming unittests
  • Octal literals
  • Atomic!T (instead of relying on shared, I assume)

Otherwise, I’m not considering it because there are too few likable additions, but it is still an admirable effort.

Wishlist

Since I still enjoy D, I’d like something like D.

What I look for (which D provides!):

  • Highly portable system language.
  • Stability. Don’t want the compiler being the only breathing specification on a nightly basis.
  • Ease of use, for faster prototyping/scripting. That includes commands for project management.
  • Practical and simple language, not based on ideals only. Work with low and high level paradigms.
  • Be able to create bindings and deal with system libraries. System ABI.
  • POD structures with alignment options.
  • Pointers. Slices.
  • RAII (scope/defer/struct dtor).
  • In-source unittesting.
  • Use git links directly as external packages with pinning. (DUB can do that!)
  • Optional: Strong compile-time engine. D’s CTFE is among the strongest ever.
  • Optional: Exception handling. Helps with tracing. But I can understand people misusing try-catch clauses. Otherwise, need __FILE__ and __LINE__.
  • Optional: GC. We fixed memory safety issues in the 90s. Helps with prototyping. But I can understand some people might have legitimate high-performance concerns. Also still have access to manual allocation.
  • Optional: Be able to compile on low-end hardware.

What I’m not looking for (which also D provides):

  • C++ extern: I’ve always avoided C++ libraries out of portability and usage concerns.
  • Inline assembler: Saw little use in my lifetime. If I do anything serious again, do it with a dedicated assembler and static libraries.

Now, in 2026, these are my options (based on my vibes):

  • Go: https://go.dev/
    • ❤️ Highly portable. (counted 39 platforms)
    • 👍 Serious option with momentum. Still sees enterprise usage.
    • 👍 First class support for git links as packages with pinning.
    • 👍 Similar usage to DUB (i.e., go test).
    • 👍 Simple language.
    • 👍 Fast compile time.
    • 🆗 No exceptions, but can rely on errors package: errors.New("something went wrong"). Uses runtime package for stack trace and frame information.
    • 🆗 Unittesting is done with “*_test.go” file names and with go test.
    • 🆗 Cgo can make C interfaces for Go programs. But apparently is a little messy and costly.
    • 🆗 Has defer (LIFO), but only for function scope.
    • 👎 Effectively no compile-time feature. Needs go generate with external tools, otherwise limited to constants (len("string") allowed).
    • 👎 GC is non-optional and pervasive. malloc/free is Cgo territory.
  • Rust: https://rust-lang.org/
    • ❤️ Supports more platforms these days. (counted 32 platforms)
    • 👍 Very popular. Used in enterprise.
    • 👍 Cargo is very similar to DUB. Compiler is separate.
    • 👍 Unittesting inside source using #[cfg(test)] mod tests {}. Acts as its own isolated module. Use cargo test.
    • 👍 Cargo can use git links as packages with pinning.
    • 👍 Drop trait (LIFO, block scope) and defer! macro.
    • 🆗 No exceptions, deal with Result<T, E> tuple. std::backtrace::Backtrace (Backtrace::force_capture()) for backtraces since Rust 1.65. Has file!() and line!() macros (useful with #[track_caller]).
    • 🆗 Subset of Rust can run with const fn functions at compile-time. Otherwise, see macros.
    • 👎 Compiler requirements are a very fast moving target due to frequent ecosystem shifts.
    • 👎 Interfacing with C is tedious and requires FFI.
    • 👎 Still a rigid and verbose language due to borrow checker.
    • 👎 Slow compile time.
  • Zig: https://ziglang.org/
    • 👍 Good platform support. (counted 23 platforms)
    • 👍 Unittesting inside source using test "do thing" {} and zig build test or zig test src/main.zig.
    • 👍 Use C headers directly. No FFI. zig cc.
    • 👍 defer and errdefer (like D’s scope(failure)). Both block-scoped.
    • 👍 Fast compile time.
    • 👍 comptime comes close to D’s CTFE in features.
    • ❓ Unknown enterprise usage.
    • 🆗 No exceptions, but there are named tags. @errorReturnTrace() for stack traces. @src() for file/line. @returnAddress() for callee. Interesting error path travel approach.
    • 🆗 Can use git packages, but have to manually pull archives. Can be tedious to set up.
    • 🆗 Build system is entirely scripted (build.zig), not declarative. (Matter of adjustment)
    • 👎 C++ reinvented. Rigid and verbose language due to explicit allocation.
    • 👎 Not stable, technically speaking. (Sure, I can deal not having access to hardware tabs)

Tough choice, huh?

Well, Claude, what do you have to say?

None of them fully replace D for your specific wishlist. Go matches your workflow preferences (simplicity, scripting, git packages, GC, stability) but not your systems-level needs. Rust matches your systems-level needs (RAII, alignment, slices, C interop) but fights your workflow preferences. Zig is philosophically the closest to D’s “practical systems language” niche but isn’t stable yet.

If I were to pick choices now: Go for the workplace (easiest to adjust to) and Zig for personal code; If D were to disappear tomorrow.

My heart tells me Zig might be the closest thing on a workflow-system approach. It is a matter of adjusting. Even then, I’ll keep an eye out for companies and projects using Zig.

For the fun of it, let’s look at other options in the wild:

  • Odin: https://odin-lang.org/
    • Decent support list. 64-bit operating systems only. 11 platforms.
    • Error management style same as Go.
    • Able to interface with C via proc "c" () (and a few more externs).
    • Never plans to support a package manager officially.
  • V: https://vlang.io/
    • Built on top of Go with differences.
    • Some of these points are nice: Smaller runtime, optional GC, zero-cost C interop, GUI library.
    • Can translate C to V easily.
    • Interesting memory management options.
  • Vale: https://vale.dev/ (Not to be confused with Vala)
    • Single ownership model seems interesting.
    • Alpha 0.2. Still experimental.
  • Hare: https://harelang.org/
    • Lack of interest in Windows due to QBE. macOS port is 3rd party.
    • Absolutely wants hardware tabs for indentation.
    • Error handling is easy to miss with ! suffix to end of calling function.
    • Exclusively manual management.
    • “fits on a 3.5-inch floppy”. (320K, 720K, HD 1.44M, or an HiFD 200M floppy?) Well, do you still use floppy drives?
  • Carbon: https://github.com/carbon-language/carbon-lang
    • Builds on C++, and thus is compatible on an ABI level. Available where a C++ compiler is. Still experimental.
  • Bolin: https://bolinlang.com/
    • “Automatic memory management (no gc, no rc)”
    • macOS and Linux.
    • Obviously incomplete.
  • Jai: https://inductive.no/jai/
    • Not publically available. (Still?)
  • Red: https://www.red-lang.org/
    • GC, low-level, concurrency, parallelism, REPL, multiple platforms…!
    • Interesting syntax. A little too declarative. (JSON is a subset of JS, this feels like the opposite!)
  • Neat: https://neat-lang.github.io/
    • A hard fork of D.
    • “It is hilariously not” ready for production.
  • Crow: https://crow-lang.org/
    • Gone…? Gone.
  • Jule: https://jule.dev/
    • Windows, macOS, Linux.
    • Compiles to C++ (cites Clang as a backend, doc confirms compiled to C++).
    • Reference-counted GC.
    • “Interoperability with C and C++ is a first-class design goal in Jule.”

That’s a bummer. Guess I’ll go learn some Go leetcode in the meanwhile. Anyone hiring Go devs?

Until then, I’ll keep being able to randomly generate mangled function names at compile-time entirely within the language.