This Week in D August 7, 2016

Welcome to This Week in D! Each week, we'll summarize what's been going on in the D community and write brief advice columns to help you get the most out of the D Programming Language.

The D Programming Language is a general purpose programming language that offers modern convenience, modeling power, and native efficiency with a familiar C-style syntax.

This Week in D has an RSS feed.

This Week in D is edited by Adam D. Ruppe. Contact me with any questions, comments, or contributions.

Statistics

In the community

Community announcements

See more at the announce forum.

Tip of the Week

There is a hidden magical variable in druntime called rt_trapExceptions that you can set to false to cause uncaught exceptions to abort the program. But, since this is checked before any of your code is run (including static constructors!), you need to declare your own C main to set it:

extern extern(C) __gshared bool rt_trapExceptions;
extern extern(C) int _d_run_main(int, char**, void*);

extern(C) int main(int argc, char** argv) {
        rt_trapExceptions = false;
        return _d_run_main(argc, argv, &_main);
}

int _main() {
        try {
                throw new Exception("no break, no abort!");
        } catch(Exception e) {}

        import std.stdio;
        writeln("here!");

        throw new Exception("this will abort");
        return 0;
}

The first two lines simply give us access to the druntime symbols. The first extern means they are declared in some external module. The next extern(C) is the linkage and name mangling. Leaving either off will result in linker errors.

rt_trapExceptions is the magic variable we want to set. It is true by default - the D runtime will normally catch exceptions and print their message before terminating. _d_run_main is a helper function inside the runtime that translates C arguments into D arguments, initializes the runtime, and calls your function - it does all the magic to get from the C world that the runtime starts in (well, starts in at the point of the C main!) to the D world that your typical D main expects.

On Windows, you can also simply run your program in a debugger. The runtime will recognize this and automatically skip the exception trap, so the debugger will break automatically on an uncaught exception.

By calling it, we reuse the core runtime's initialization and teardown functions.... but by doing our own extern(C) main, we get a chance to inject our own code before and after!

The next lines do exactly that: they declare a C-style extern(C) main, set the magic rt_trapExceptions to false so it won't swallow our exceptions, then call our own D-style main. It isn't exactly a D main: it needs to be called something else (otherwise, the linker will complain that there are two mains, because the D compiler will insert a C main automatically when it sees a D main, and we want our own...), and it needs to return a value, like C main is supposed to do (in standard D, it is legal to return void from main, and the compiler will automatically rewrite it to return 0, but here, we're bypassing that automatic sugar.).

Otherwise, though, our _main works just like a D main: it may receive string[] args, call D functions, etc.

But, thanks to our injected rt_trapExceptions code, now exceptions work a bit differently. Running that program on Linux will show:

$ ./except
here!
uncaught exception
dwarfeh(224) fatal error
Aborted

You might be thinking "that sucks, I like the printed message!". Well, then use it! But, consider this in the debugger:

$ gdb except
gdb: /lib64/liblzma.so.5: no version information available (required by gdb)
GNU gdb (GDB) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-slackware-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from except...done.
(gdb) r
Starting program: /home/me/test/except
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
here!["/home/me/test/except"]
uncaught exception
dwarfeh(224) fatal error

Program received signal SIGABRT, Aborted.
0xf7d973c4 in raise () from /lib/libc.so.6
=> 0xf7d973c4 <raise+68>:       87 fb   xchg   ebx,edi
(gdb) where
#0  0xf7d973c4 in raise () from /lib/libc.so.6
#1  0xf7d99090 in abort () from /lib/libc.so.6
#2  0x0808b228 in _d_throwdwarf ()
#3  0x0807e4f1 in except._main(immutable(char)[][]) (args=...) at except.d:17
#4  0x0808b05b in rt.dmain2._d_run_main(int, char**, extern(C) int(char[][]) function*).runAll().__lambda1() ()
#5  0x0808afe5 in rt.dmain2._d_run_main(int, char**, extern(C) int(char[][]) function*).tryExec(scope void() delegate) ()
#6  0x0808b017 in rt.dmain2._d_run_main(int, char**, extern(C) int(char[][]) function*).runAll() ()
#7  0x0808afe5 in rt.dmain2._d_run_main(int, char**, extern(C) int(char[][]) function*).tryExec(scope void() delegate) ()
#8  0x0808af3f in _d_run_main ()
#9  0x0807e44b in main (argc=1, argv=0xffffd564) at except.d:6
(gdb) up 3
#3  0x0807e4f1 in except._main(immutable(char)[][]) (args=...) at except.d:17
17              throw new Exception("this will abort");

I'll spare you the rest of the session, but suffice it to say the debugger is able to show you the message, stack trace, and much more - you have the full interactivity and introspection it offers, right at the point of the thrown exception.

Contrast that to the session with the default rt_trapExceptions setting:

GNU gdb (GDB) 7.10
<snip>
(gdb) r
Starting program: /home/me/test/except
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
here!
object.Exception@except.d(17): this will abort
-- --------------
except.d:17 int except._main(immutable(char)[][]) [0x807e4f0]
??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x808b05a]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x808afac]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0x808b016]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x808afac]
??:? _d_run_main [0x808af3e]
except.d:6 main [0x807e44a]
??:? __libc_start_main [0xf7d8029f]
[Inferior 1 (process 10928) exited with code 01]
(gdb) up
No stack.

More info printed to stderr... but much less information available to the interactive debugging session.

Moreover, unlike setting a breakpoint at d_throw (the exact name of this function is platform-specific, by the way), thrown and caught exceptions in the program's normal flow proceed normally without interrupting you - only uncaught exceptions trigger the debugger.

You can also configure your system to save a core dump on this event for analysis later - potentially far more useful than writing the exception's error message to a log!

Hopefully, druntime will expose this variable in a more easier way in the future. Currently, on Windows, it is automatically set if a debugger is detected, but on Linux, debugger detection is quite a bit harder and hackier, so it might not happen any time soon. Druntime could (and should!) also potentially offer it as a command line switch, like it does with GC profiling now.

DRUNTIME DEVS: if you read this, make it happen!

But, until then, the extern(C) main trick can inject the code, and besides, knowing the technique is useful for other things too :)

Learn more about D

To learn more about D and what's happening in D: