Skip to content
C Interop and betterC
step 1/4

Reading — step 1 of 4

Learn

~2 min readConcurrency, Mixins, C Interop

D was designed for smooth C interop. Plus a -betterC mode that disables D's runtime entirely.

Calling C from D

import std.stdio;

extern (C) int strlen(const char* s);
extern (C) char* strcpy(char* dest, const char* src);

void main() {
    const char* s = "hello";
    writeln("length: ", strlen(s));
}

extern (C) declares the symbol uses C calling convention. The C standard library is available without explicit imports — most linkers find strlen automatically.

For stdlib bindings, core.stdc.* modules:

import core.stdc.stdio;       // printf, fopen, etc.
import core.stdc.stdlib;      // malloc, free
import core.stdc.string;      // strlen, strcmp

D pointers and C pointers

They're identical at the binary level:

import core.stdc.stdlib;

int* p = cast(int*)malloc(int.sizeof * 100);
scope (exit) free(p);
p[0] = 42;
writeln(p[0]);

C++ interop

extern (C++) {
    class Foo {
        void doThing();
    }
}

D can call C++ classes too — including non-virtual methods, virtual dispatch, even C++ templates (with extern(C++) class).

Calling D from C

Mark D functions as extern(C):

extern (C) int compute_thing(int x) {
    return x * x;
}

The C side declares it normally:

extern int compute_thing(int x);

Compile D with dmd -c to produce an object file, then link with C code.

betterC mode

Compile D without the runtime — no GC, no exceptions, no module ctors:

dmd -betterC main.d

Useful for:

  • Embedded systems (microcontrollers)
  • Bootloaders / kernels
  • Calling D from non-D languages without runtime overhead
  • Linking into existing C/C++ projects without disrupting their model

In -betterC:

  • No class (use struct only)
  • No throw/catch (use error codes)
  • No string (use char* or const(char)[])
  • No standard library (use core.stdc.*)
  • Templates and CTFE still work
// betterC example
extern (C) int main() {
    import core.stdc.stdio;
    printf("hello, betterC\n");
    return 0;
}

This compiles to a tiny binary — a couple KB instead of D's typical 1 MB+ minimum due to runtime.

ImportC

D 2.097+ can import C code directly:

import mylib;        // mylib.c parsed at compile time

No bindings needed. The D compiler parses C and exposes symbols.

Why this matters

D can drop into existing C/C++ codebases without forcing a rewrite. Mix D with C for the parts where D's productivity helps; keep C for the legacy core. This is how Sociomantic and Weka built large D systems on top of C foundations.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…