This is extended content from the D Blog post, Go Your Own Way (Part Two: The Heap).

Calling into C confusion?

Recently, Walter Bright wrote an article, ‘D as a Better C’, describing D’s “betterC” mode. To be clear, the -betterC command line switch is not needed to make use of any C API, including the C standard library. This switch simply eliminates the automatic linking of DRuntime and Phobos (the D standard library), and disables certain language features that depend on the runtime. With -betterC, the C standard library is still available, but it’s also always available without it.

Nor is it necessary to declare functions that call into C as extern(C). This is a linkage attribute which declares a function to have the calling convention commonly referred to as cdecl. Functions from C APIs must be annotated as such in D unless they use a different calling convention, such as the stdcall convention used by the Win32 API (extern(Windows) in D). It also allows functions in D libraries to be callable from C.

When a D program is compiled, the compiler generates a function extern(C) int main(int argc, char **argv), which is the entry point for the D side of the house and where DRuntime initialization takes place. The only time this does not happen is when the compiled code contains a custom, non-D main function, such as one of the following:

// This one...
extern(Windows) int WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { ... }

// Or this one...
extern(C) int main(int, char**) { ... }

In both cases, the same default libraries (the C standard library, DRuntime, and Phobos) will still be linked with the exectuable, but now DRuntime must be initialized manually. When compiled with -betterC, only the C standard library is linked by default.

It’s a somewhat common misunderstanding among new D programmers that extern(C) is required on functions that contain calls to C APIs. It isn’t. A linkage attribute has no impact on what happens inside a function, only on how the function is called and how its symbol is mangled. The default linkage attribute for D functions is extern(D).

There were also some confused comments about Walter’s article. You don’t need -betterC to interact with C from the D side (and technically not from the C side, but that actually is out of scope here). Also, please keep in mind that most of the blog post does not apply to D code compiled with -betterC.

Hopefully, this information will help readers new to D in understanding the bigger picture when perusing the rest of this section.

Options for handling malloc failures

The examples in the main blog post opted for assert(0, "Out of memory") to handle malloc failures. This is possible only because assert(0) is never compiled out of the final binary, even when asserts are disabled. It’s often used for unreachable code, but works just as well for this situation.

It would be preferable to throw a custom subclass of Error indicating the failure, such as MallocFailedError, or OutOfHeapMemoryError, or some other such descriptive name (mostly to distinguish it from DRuntime’s OutOfMemoryError which is thrown on GC allocation failures). This is possible, but it’s probably best to avoid using new to do so at the point of failure. For one, if the allocation function is intended to be called from within @nogc functions, then it can’t be using new to allocate memory. For another, while a malloc failure doesn’t necessarily mean the GC has no memory, there’s no guarantee it does. But it’s possible to preallocate an instance of the appropriate class and throw it when necessary.

void[] allocate(size_t size)
{
    static MyMallocFailedError outOfMem;
    if(outOfMem is null) outOfMem = new MyMallocFailedError;

    void* ptr = malloc(size);
    if(!ptr) throw outOfMem;
    ...
}

Another option is to make use of the C standard library’s exit function, available in core.stdc.stdlib, the same place where malloc lives.

if(!ptr) exit(EXIT_FAILURE);