This Week in D November 22, 2015

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

A lot of people ask me for metaprogramming debugging tips. My biggest tip is to just remember that metaprogramming is still programming!

If you are making a code generator, make sure it runs at ordinary runtime before running it at compile time. Code with string mixins can be ugly, but it is still basically just ordinary string generation code. Inspect the generated string for correctness! Try replacing mixin(code_generation_function()) with pragma(msg, code_generation_function()) to print it at compile time, or simply use writeln or std.file.write to print it out and compile and run the program as an ordinary program with ordinary output!

	string code_generation_function() {
		return "int a;";
	}

	mixin(code_generation_function()); // metaprogramming
	pragma(msg, code_generation_function()); // compile-time printing

	// ordinary runtime printing of the same code!
	void main() {
		import std.file;
		std.file.write("generated-code.d", code_generation_function());
	}

Writing the code you intend to mixin to a file means you can format it (see dfmt for an automated tool that can handle even ugly generated code) and syntax highlight it in your editor to make it easier to read. Moreover, if the program fails for whatever reason, you can now debug it like any other program too - you can use traditional techniques from printf debugging to programs like gdb - instead of worrying about finding a compile-time debugger.

Actually, running a code generator as a separate program can be advantageous in larger programs too because you can run it as a separate step in your build process. This can help keep your build memory and time down and allow caching of the generated code. Consider this option if needed by your build constraints and circumstances.

Template metaprogramming is tougher to debug, but also tend to generally be simpler, because complex metaprograms in D tend to be written as CTFE functions rather than complicated templates. If you are having a hard time writing a template, you may wish to try to rewrite it as a regular runtime function too.

However, of course, many templates are best as templates, and rare metaprogramming tasks can only be done with templates. Templates are relatively easy to debug though: just try instantiating it and see what errors the compiler puts out.

Instantiation just means giving a template arguments:

	// this is a template definition
	struct Foo(T) {
		T member;
	}

	// so is this
	void foo(T)(T t) {}

	unittest {
		Foo!int test; // this is a template instantiation 
		foo(10); // so is this
	}
Remember, uninstantiated templates are checked only for syntax correctness, but not semantics. Always try to instantiate a template with common arguments in your own code, perhaps in a unittest block, before releasing a library to ensure it actually compiles in practice!

Since template code tends to look like ordinary code, the compiler error messages should help you get it working. The tricky bit is static if - be sure to test it with arguments that will evaluate to true and false.

You may use static assert on individual conditions inside a static if to help figure out why it is or isn't triggering:

	static if(a!T && b!T) {} // wondering why this isn't triggering when passing int?

	// add this to figure it out:
	static if(is(T == int)) {
		static assert(a!T);
		static assert(b!T);
		// compile this and one of the asserts will fail, telling you which
		// part of the if above isn't working so you can dig deeper
	}

For debugging compile-time reflection, see the above on functions: just run the CT reflection as a runtime function! The overall reflection function should be decomposable into static if branches and CTFE functions, which the above two techniques should help you narrow down your problem.

Lastly, when debugging recursive templates, I like to just look at it and reason one step at a time. Try simplifying it to just two arguments when instantiating to understand what's going wrong, then you can handle it like above again.


The general tip with all debugging is to try to reduce the problem to something smaller that you already know how to handle. The 'meta' in 'metaprogramming' can often be removed in D by just running the program at ordinary runtime, dealing with compile time separately. Since the same function works in both contexts, writing and debugging metaprograms in D means you can handle it using the knowledge you already have.

Learn more about D

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