Another major release of DMD, this time 2.078.0, has been packaged and delivered in time for the new year. See the full changelog at dlang.org and download the compiler for your platform either from the main download page or the 2.078.0 release directory.
This release brings a number of quality-of-life improvements, fixing some minor annoyances and inconsistencies, three of which are targeted at smoothing out the experience of programming in D without DRuntime.
C runtime construction and destruction
D has included static constructors and destructors, both as aggregate type members and at module level, for most of its existence. The former are called in lexical order as DRuntime goes through its initialization routine, and the latter are called in reverse lexical order as the runtime shuts down. But when programming in an environment without DRuntime, such as when using the -betterC
compiler switch, or using a stubbed-out runtime, static construction and destruction are not available.
DMD 2.078.0 brings static module construction and destruction to those environments in the form of two new pragmas, pragma(crt_constructor)
and pragma(crt_destructor)
respectively. The former causes any function to which it’s applied to be executed before the C main
, and the latter after the C main
, as in this example:
crun1.d
// Compile with: dmd crun1.d // Alternatively: dmd -betterC crun1.d import core.stdc.stdio; // Each of the following functions should have // C linkage (cdecl). extern(C): pragma(crt_constructor) void init() { puts("init"); } pragma(crt_destructor) void fini() { puts("fini"); } void main() { puts("C main"); }
The compiler requires that any function annotated with the new pragmas be declared with the extern(C)
linkage attribute. In this example, though it isn’t required, main
is also declared as extern(C)
. The colon syntax on line 8 applies the attribute to every function that follows, up to the end of the module or until a new linkage attribute appears.
In a normal D program, the C main
is the entry point for DRuntime and is generated by the compiler. When the C runtime calls the C main
, the D runtime does its initialization, which includes starting up the GC, executing static constructors, gathering command-line arguments into a string array, and calling the application’s main
function, a.k.a. D main
.
When a D module’s main
is annotated with extern(C)
, it essentially replaces DRuntime’s implementation, as the compiler will never generate a C main
function for the runtime in that case. If -betterC
is not supplied on the command line, or an alternative implementation is not provided, DRuntime itself is still available and can be manually initialized and terminated.
The example above is intended to clearly show that the crt_constructor
pragma will cause init
to execute before the C main
and the crt_destructor
causes fini
to run after. This introduces new options for scenarios where DRuntime is unavailable. However, take away the extern(C)
from main
and the same execution order will print to the command line:
crun2.d
// Compile with: dmd crun2.d import core.stdc.stdio; pragma(crt_constructor) extern(C) void init() { puts("init"); } pragma(crt_destructor) extern(C) void fini() { puts("fini"); } void main() { import std.stdio : writeln; writeln("D main"); }
The difference is that the C main
now belongs to DRuntime and our main is the D main
. The execution order is: init
, C main
, D main
, fini
. This means init
is effectively called before DRuntime is initialized and fini
after it terminates. Because this example uses the DRuntime function writeln
, it can’t be compiled with -betterC
.
You may discover that writeln
works if you import it at the top of the module and substitute it for puts
in the example. However, always remember that even though DRuntime may be available, it’s not in a valid state when a crt_constructor
and a crt_destructor
are executed.
RAII for -betterC
One of the limitations in -betterC
mode has been the absence of RAII. In normal D code, struct
destructors are executed when an instance goes out of scope. This has always depended on DRuntime, and since the runtime isn’t available in -betterC
mode, neither are struct
destructors. With DMD 2.078.0, the are in the preceding sentence becomes were.
destruct.d
// Compile with: dmd -betterC destruct.d import core.stdc.stdio : puts; struct DestroyMe { ~this() { puts("Destruction complete."); } } extern(C) void main() { DestroyMe d; }
Interestingly, this is implemented in terms of try..finally
, so a side-effect is that -betterC
mode now supports try
and finally
blocks:
cleanup1.d
// Compile with: dmd -betterC cleanup1.d import core.stdc.stdlib, core.stdc.stdio; extern(C) void main() { int* ints; try { // acquire resources here ints = cast(int*)malloc(int.sizeof * 10); puts("Allocated!"); } finally { // release resources here free(ints); puts("Freed!"); } }
Since D’s scope(exit)
feature is also implemented in terms of try..finally
, this is now possible in -betterC
mode also:
cleanup2.d
// Compile with: dmd -betterC cleanup2.d import core.stdc.stdlib, core.stdc.stdio; extern(C) void main() { auto ints1 = cast(int*)malloc(int.sizeof * 10); scope(exit) { puts("Freeing ints1!"); free(ints1); } auto ints2 = cast(int*)malloc(int.sizeof * 10); scope(exit) { puts("Freeing ints2!"); free(ints2); } }
Note that exceptions are not implemented for -betterC
mode, so there’s no catch
, scope(success)
, or scope(failure)
.
Optional ModuleInfo
One of the seemingly obscure features dependent upon DRuntime is the ModuleInfo
type. It’s a type that works quietly behind the scenes as one of the enabling mechanisms of reflection and most D programmers will likely never hear of it. That is, unless they start trying to stub out their own minimal runtime. That’s when linker errors start cropping up complaining about the missing ModuleInfo
type, since the compiler will have generated an instance of it for each module in the program.
DMD 2.078.0 changes things up. The compiler is aware of the presence of the runtime implementation at compile time, so it can see whether or not the current implementation provides a ModuleInfo
declaration. If it does, instances will be generated as appropriate. If it doesn’t, then the instances won’t be generated. This makes it just that much easier to stub out your own runtime, which is something you’d want to do if you were, say, writing a kernel in D.
Other notable changes
New users of DMD on Windows will now have an easier time getting a 64-bit environment set up. It’s still necessary to install the Microsoft build tools, but now DMD will detect the installation of either the Microsoft Build Tools package or Visual Studio at runtime when either -m64
or -m32mscoff
is specified on the command line. Previously, configuration was handled automatically only by the installer; manual installs had to be configured manually.
DRuntime has been enhanced to allow more fine-grained control over unit tests. Of particular note is the --DRT-testmode
flag which can be passed to any D executable. With the argument "run-main"
, the current default, any unit tests present will be run and then main
will execute if they all pass; with "test-or-main"
, the planned default beginning with DMD 2.080.0, any unit tests present will run and the program will exit with a summary of the results, otherwise main
will execute; with "test-only"
, main
will not be executed, but test results will still be summarized if present.
Onward into 2018
This is the first DMD release of 2018. We can’t wait to see what the next 12 months bring for the D programming language community. From everyone at the D Language Foundation, we hope you have a very Happy New Year!