The (D) Programming Language

We already had a C-killer but nobody knew about it



This post assumes the -betterC flag of the D compiler

Amid many new programming languages that put on their pipeline, as a replacement to the Greybead’s developers favorite programming language, which is C, none still can fully replace C as in it entirety, first because most are not yet stable, or they offer a huge set of deviated features, that I may say that don’t aggregate, but could also mean nothing, but my very subjective opinion. D is an underrated language created in 2001, just 16 years younger than C++, but still older than many other languages. This alone is enough to separate D from the unstable C-like languages. For context, the “C Killer” languages I’m referring to are: C++, C3, Zig, Odin and Rust. I can’t tell about X language I don’t cover, because I never wrote a single line of code in any other, but these. I strongly believe D is far superior to all these languages I’ve listed. Here is a far or less decent break down.

1 C++

D has way more builtin features than C++ has. Because I’m here to talk about -betterC feature, where the usual D standard library (Phobos) isn’t available, and I’m also talking about fully replacing C, we can also say nostdlib++ for C++. C++ is awful is this regard, it will want you to use the standard library no matter what, even if I don’t want it. So where does D feels so much better than C++?

D has slices as a builtin with bounds-checking, something that you can mimic in C++, but it will make you develop some sort of a slices.h library for this… again D has it as a builtin.

Many operator overloadings towards strings are also builtins in D, and you can also do the same in C++ with some manual work.

D has contracts. I remember contracts being a thing in C++23, but because newer standards of C and C++ are slowly adopted by other compiler that are not gcc neither clang, it’s hard to assume people are using the latest standard. I can consider C++ don’t having it, I guess.

D has type reflection at compile-time without runtime costs, or at least it seems. C++ can have type instropection at runtime with RTTI (Run-time Type Instropection). D also has runtime instropection, but only if the GC is enabled.

D doesn’t have header files, doesn’t have preprocessor and can leverage a much better namespace system and other compile-time features that fully takes the need for the C preprocessor. That’s very good, comparing with C, since generating functions with the preprocessor obliterates the debugging information, and you put yourself in a very bad spot for debugging your program later.

D templates have a less weird syntax. As an example, Result!int for D and Result<int> for C++.

I’m not sure how flexible constexpr is, but at least in D, anything that the D compiler can see, it can run at compile-time.

D has a way easier to use defer like experience without RAII: scope(exit).

The D compiler in my desktop and in Termux is slighly faster than the C compiler, gcc and clang, respectively. But C++ without the standard library can still be slower than C.

D has a module-system as the replacement for the simple preprocessor style for including code. Although it can have namespace collisions, it’s way easier to resolve it (import io = core.stdc.stdio), differently from C++ (use namespace namespace_name).

D has builtin support for vectors with core.simd. Yes, it also works in -betterC.

Last, but not least: D can just include C code and compile normally. Can your C++ do that? I bet you have to extern "C" first, and fix all edge cases where C++ doesn’t know what to do when a pointer has a type it doesn’t know what overloading to use. The best part of it, is that D can even namespace the included C code.

2 C3

C3 is not bad, but it’s very, not joking, very opinionated language. You can’t just break the convention, or the code won’t compile. This has a very good reason, where the language is so strict, that crafting tools to parse C3 won’t require a preprocessor, lexer and parser like C.

Many “killing features” of C3, are already provided by D, this includes (from the mainpage) contracts, C ABI compatibility, module system, operator overloading, compile-time code, generic code, inline assembly, and type introspection. I personally think C3 error handling less powerful than Zig, which is similar, but more polished. D error handling is based on try-catch only available through GC, so with -betterC just return as values error handling is acceptable. Another thing, D can’t catch-up is stack traces. C3 has a simple stack trace mechanism, but that still helps a lot, and a few easy to make yourself signal handler that can catch segmentation fault, and other invalid memory access. Why “easy to make yourself” you may ask? C3 implements that in the standard library, so if you disable it, begone stack traces. A new challenger approach: “segmentation fault”.

D can catch many memory issues at runtime, if you use ref to guarantee pointer are never going to be null, slices for bounds-checking and contracts. C3 does a better job, by doing these for you.

C3 contracts are far more verbose than D, with same outcomes.

Another thing that makes me think D is better than C3, is when we are talking about C3 + standard library. It takes a reasonable amount of time to compile, and links with several symbols. This is also something that happens in Zig and Rust. I’m not a fan of it.

C3 also has a weird behavior of trying to hide things from you. A great example is @pool. There is not such evidence in how to use it in the standard library. I’ve not inspected the c3c source code to find more about it. In the documentation at least, @pool is always commented around the temporary allocator. My hypothesis is that the temporary allocator is an arena, and the @pool basically tells where the arena_destroy or arena_reset must be called. I might be wrong here.

These are the most things I feel C3 and D may highly differ. D an C3 have many other features in common, which only stability can tell which one is more reliable to be used right now. However, C3 is a way more promising language with many other features on the roadmap to 1.0, which can, perhaps, change my idea. Just to note a few in verbatim from 2026-06-07:

A secondary backend for fast compilations
A completely fleshed out standard library
A C output backend

3 Odin

Odin, if wasn’t unstable, would be the nicest C alternative. It keeps simplicity as the ultimate thing. You won’t have a OOP syndrome diagnosis, since the language is data-centric and not such method syntactic sugar exists, but many facilities are present for the “Joy of Programming”.

It really tries to be very close to C, in the sense of actually addressing the pain points of writing C code. It’s also very easy to use in the sense of batteries-included, since it ships with so many libraries out of the box. Just a shame that interfacing with C requires bindings, unlike D or Zig.

Ginger Bill manages to do a impressive work in designing a language. I notice that, on generics, for example: Result(int) instead of Result<int>, as the same example from C++. But why that? Here is a more exaggerated example:

HashMap(int, HashMap(int, int))

HashMap<int <HashMap<int, int>>

I ask you: Which one is easier to read?

I can only say D is better, because it’s more mature, but Odin has the most potential of all of these languages I’m covering. Unless, if Jai is released, and the community shadows Odin. But, even then I still think that Odin would be more reliable, since you won’t have a chance to lose yourself in macros or by any other advanced features that can overcomplicate things.

Still, I can’t compile and run Odin in Termux aarch64 in peace, without doing a LLVM workaround, which costs 8 seconds of compile and linking time. Just for comparison, compiling D in the same environment takes 0.400ms. Of course, my hardware is old and slow, and I love to use it to measure performance, because they stand out more.

4 Zig

Zig is a C alternative, but I wouldn’t name it a C-killer. These two languages are very different on how they want to achieve the same goal. Zig feels more OOPish, and that’s clear by reading the standard library. The concept of OOP is somehow broken, because there is no such “feature” that defines OOP, at least my definition, for clarification, is the simple fact of having a vtable, where the vtable also has associated data with it, that can be modified.

Zig is also more overengineered than the previous languages, for the good reasons, but also with the tradeoffs (everything has a tradeoff, that’s why even someone like me tried to make a programming language, but abandoned after realizing the caveats of my approach). Zig has clear purposes, which the “reusable code” means more for the standard library. With the very recent Io overhaul, it’s far easier to write code that can be reused in different contexts, without code changes. This comes with the cost, where learning Zig now is harder than before.

Nonetheless, Zig does a couple of things that really catch my attention. The allocator system and runtime checks are very robust. You have no idea of it, until you try it yourself. If ever need to write code that might be the so known “unsafe”, Zig is perfect for that. It will simply aid you on what you are trying to do, instead of doing nothing. It TELLS you where you have a memory leak at runtime, which the exact place in the code, where that happened. Tried anything sneaky will mostly result in a panic, instead of “segmentation fault”. I haven’t tried Zig without the standard library, to check rather that’s a std feature or not. I’ll owe you that.

But, but! The D module system makes a strong contender, based on this quote: “Zig is better at using C libraries than C is at using C libraries”. This quote has been takes from this PDF at page 13: freebsd.org/attachments/zig3-pdf.18027. Awesome read, by the way, I recommend it, if you are interested in Zig. This quote means, that Zig can consume C code without writing Zig bindings and it just works fine. Not only to mention that you get namespacing as well. D can do the same.

D is rather a much better C alternative than Zig, by using the argument that D is simpler and more C-ish, with the added features for more flexible code and safety. But if the topic is reliability, the Zig restrictive compiler gives better guarantees about the code.

5 Rust

Where the industry is going towards. Even Linux is adopting Rust. This is such of a red flag, that I, honestly, may consider leave Linux in the future for FreeBSD.

Rust has a different goal: Memory safety. That’s fully thrown in the trash if you use unsafe once in your code. It’s equivalent of writing C, not compiler guarantees, no checks, and nothing. I personally think this is a very dirt way to market a language, just like Java did with it’s “write code once, run everywhere”. Let me argue.

For the Java thing: C can run everywhere. The only thing that changes are platform specific APIs, which must be handled manually.

For the Rust thing: If you don’t provide 100% memory safety don’t market it as memory safe. “Memory safe” is a fact, not a percentage.

But this isn’t the worst thing about Rust, and this very next thing I’m going to tell you also applies to MANY other languages, it’s not a Rust problem alone.

Languages like C, D, Zig, Rust, V and Nim, for example, are all self hosted. For more context, you should read Reflections On Trusting Trust by Ken Thompson. This is all about compiling the compiler with the compiler, without bootstrapping. I’m not sure about how to bootstrap Zig, but I supposed that solved this problem. But for the other languages, the next version of the compiler can only be compiled with a previous version, which lets bad actors write malicious code in the compiler no present in version-control and that is propagated indefinitively.

If you compile a program like this: int main() { return 0 } in C and D right now, and inspect the executable with nm you will find a couple of symbols in common, and a couple more for D. But if you do the same thing for Rust: pub fn main() { return }, you will give 1300 symbols and a 500kb binary. What is this bloat? There is no such evidence that this is malware, and not such evidence that this isn’t malware. What a beautiful thing! I can’t trust this language. These symbols are neither stripped with higher optimizations. What if you run strip than? Still 300kb. Do you know assembly? I don’t, but how do you make a assembly program is equivalent to that Rust one?

.section .text
.globl _start
_start:
    mov $60, %rax  # sys_exit
    xor %rdi, %rdi # zeroed rdi
    syscall        # exit(0)

What is it!? 7 lines of assembly, that becomes just one symbol in nm and 760 bytes of binary size? What is even it? Rust is doubtful. Even with mass adoption and agentic usage in scale, to operate on things like rewriting 1 million lines of Zig from Bun in Rust? Canonical rewritting the coreutils in Rust? People saying that ripgrep is better than grep? Come on! You gotta be kidding me.

This situation could only get worse with crates.io, which has the same vibes of npm.

No way on planet Earth, Rust is the way to make software safer. Ignore all I said, unsafe alone not aiding you to write safer unsafe code is enough. And Zig totally does a better job at makes unsafe code safer, hands down. Okay, I’m calm now.

And by the way. You can have many nice features from Rust in D. And if you want a language with a borrow checker, but that is not Rust, checkout Seed7, a beautiful project maintained by a single guy (total of 8 contributors) for decades now, that came with the borrow checker idea, before Rust, that is also cross-platform and works even in the browser.

6 Wrap up

D is underrated, try it out.

This post is licensed under CC BY 4.0 by the author.