Skip to content
Lesson 7 of 7

Step 1 of 4 · Reading · ~2 min

Learn

Concurrency, 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) size_t 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 that the symbol uses C linkage and the C calling convention. libc is already linked in, so a declaration is enough to reach it - but the declaration has to match C's real signature. strlen returns size_t, not int, and getting that wrong is a silent ABI bug rather than a compile error.

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

Recent DMD versions 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…