100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

BetterC and Systems Programming

The -betterC compiler switch strips D down to a GC-free, runtime-minimal subset ideal for embedded, kernel, and other systems-programming contexts where a full runtime isn't an option.

Practical DAdvanced10 min readJul 10, 2026
Analogies

What -betterC Removes and Why

-betterC compiles D without the full D runtime, druntime: no garbage collector, no full exception unwinding across arbitrary throw sites, no TypeInfo-driven features like growable dynamic arrays or built-in associative arrays that need runtime support, and no automatic module constructors -- leaving a language that's still far more expressive than C, with templates, contracts, and RAII via struct destructors, but that links like a tiny C program with none of the runtime baggage, making it viable for microcontrollers, kernels, and any environment where you can't assume a GC or exception unwinder exists.

🏏

Cricket analogy: T20 cricket strips away the extra overs, extended field-restriction phases, and rest days of a Test match while keeping the core rules of batting and bowling intact; -betterC strips away the GC and full runtime while keeping D's core language features like templates and RAII intact.

What You Can and Can't Use Under -betterC

Under -betterC you can still use templates, structs with destructors for deterministic RAII, bounds-checked slices, and contracts compiled as plain asserts -- but you lose the GC entirely, so no dynamic arrays that grow via ~= and no built-in associative arrays unless you bring a GC-free alternative, no full try/catch/finally across arbitrary throw sites without extra scaffolding, and no module static constructors running automatically; memory must be managed manually via core.stdc.stdlib's malloc/free or via RAII structs with explicit destructors.

🏏

Cricket analogy: A franchise's overseas replacement player can still bat, bowl, and field exactly like everyone else, but doesn't get the same central contract retainer or accrued domestic pension the full squad gets; -betterC code still gets templates, structs, and RAII, but doesn't get the GC's automatic memory management.

d
// betterc_ring_buffer.d
import core.stdc.stdlib : malloc, free;

struct RingBuffer(T) {
    private T* data;
    private size_t capacity;
    private size_t head, tail, count;

    this(size_t cap) {
        capacity = cap;
        data = cast(T*) malloc(T.sizeof * cap);
    }

    ~this() {
        if (data) free(data);
    }

    void push(T item)
    in (count < capacity, "RingBuffer full")
    {
        data[tail] = item;
        tail = (tail + 1) % capacity;
        count++;
    }
}

extern(C) void main() {
    auto buf = RingBuffer!int(16);
    buf.push(42);
}

// Build with: dmd -betterC betterc_ring_buffer.d

Practical Use Cases: Embedded and Kernel Code

-betterC targets microcontrollers, such as ARM Cortex-M via ldc2's cross-compilation, OS kernel modules, and any Linux or Windows driver-style code, and it's also a common bridge for calling D libraries from C++, Rust, or other GC-free ecosystems where linking druntime would be unacceptable bloat or would conflict with the host's own memory management; a well-known pattern is writing performance-critical, allocation-free hot paths in -betterC D and linking them into a larger regular-D or C application.

🏏

Cricket analogy: A specialist death-overs bowler is brought on only for the specific high-pressure final overs where their exact skill set matters most, not the whole innings; -betterC D is brought in only for the specific allocation-free, no-runtime hot path where its exact constraints matter, not the whole application.

ldc2, the LLVM-based D compiler, is the compiler of choice for embedded and cross-compiled -betterC work because it targets ARM, RISC-V, and other embedded architectures via LLVM's backend, while dmd's -betterC support is primarily aimed at x86/x86_64 desktop and server systems programming.

Under -betterC, throwing an exception with 'throw' still compiles, but there is no unwinder to catch it across most contexts without extra runtime support -- an uncaught throw in -betterC typically calls a trap or abort rather than propagating cleanly. Treat -betterC code as effectively exception-free and use return codes, out-parameters, or Result-style structs for error handling instead.

  • -betterC removes druntime -- no GC, no full exception unwinding, no automatic module constructors -- for a C-sized runtime footprint.
  • Templates, structs with destructors for RAII, contracts-as-asserts, and D's type system all still work under -betterC.
  • Dynamic arrays that grow via ~= and built-in associative arrays need GC support and generally aren't usable under -betterC.
  • Manual memory management via core.stdc.stdlib malloc/free, or RAII struct destructors, replaces the GC for -betterC code.
  • ldc2 is the typical choice for cross-compiling -betterC D to embedded targets like ARM Cortex-M via LLVM.
  • Uncaught throw in -betterC code generally aborts rather than unwinding cleanly -- treat it as effectively exception-free.
  • A common pattern is isolating just the allocation-free hot path in -betterC and linking it into a normal GC-enabled D or C application.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#DProgrammingStudyNotes#BetterCAndSystemsProgramming#BetterC#Systems#Removes#Can#StudyNotes#SkillVeris#ExamPrep