First of all don’t take this article to negative. I really like the D programming language and often when I have to go back to C++ coding im sitting in front the code thinking “Oh this would be so much easier in D” or “This could be solved much cleaner in D”.
I recently completed 3 Projects using the D 2.0 programming language:
– A implementation of the Light Propagation Volumes algorithm
– A 3d multiplayer cross platfrom (windows / linux) space shooter
– A lisp interpreter (with almost all Scheme features)
The two 3D projects have been done using OpenGL and are very performance critical. You only get to spend 16ms every frame until it has to be completely rendered otherwise you won’t reach fluent framerates. The lisp interpreter was performance critical too, but more of a test how good D is usable as a systems programming language.
1. Assert
Assert throwing a exception is very unhandy for debugging a application, especially when some of the stacktracing and stack unwinding happend before the debugger actually gets the signal. This often lead to not beeing able to see where the assert actually triggered. Usually I had to look it up in the console output, set a breakpoint at the perticular assert and rerun the application which added quite some turnaround time. Using a custom assert is also not possible because the compiler will complain about certain “not reachable” problems. Only the builtin assert is recognized by the compiler and there is no way of flagging another function as assert. Either the builtin assert should be configureable so that it triggers a breakpoint before throwing the exception, or there sould be a way to built a custom assert because something like this feels wrong:
1 2 3 4 5 6 7 8 9 10 |
bool bar(){ switch(foo){ case 1: ... default: ... } myAssert(0,"not reachable"); assert(0,"not reachable"); } |
2. Emplace requiring the exact constructor argument types
Using D with manual memory management is a very nice thing, besides not beeing able to use phobos then and having to be very carefull when handeling arrays. But as the new and delete operator are deprecated one problem within phobos should be adressed:
When using emplace to create a struct or class emplace is not aware of the consturctor overloading rules and unless given the exact same types as the constructor it is not able to construct the object:
1 2 3 4 5 6 |
class foo { this(int* addr){ ... } } void[__traits(classInstanceSize,foo)] mem; emplace!bar(mem,null); //this will not compile |
3. Structs not having identity
Structs in D don’t have identity, meaning they can be copied around by the compiler whenever he wants. This was especially a problem when implementing a custom GC for the Lisp interpreter. Because I wanted to implement a Baker GC which requires to know the addresses of all references on the stack I wanted to do something like this:
1 2 3 4 5 6 7 8 9 10 11 |
struct Ref(T) if(is(T == class)){ T m_Ref; this(T pRef){ m_Ref = pRef; myGC.addRoot(&m_Ref); } ~this(){ myGC.removeRoot(&m_Ref); } } |
Unfortunaltey when the destructor gets called m_Ref can have a completely differend address and therefore can not be deregistred at the garbagecollector. I ended up adding a assert into the desturctor telling me when the address changed and not using any exception handling because I figured that the struct copying then will not happen. But that is not a acceptable solution for the problem. Adding one level of indirection to every reference is also not acceptable for performance reasons. Now I’m not saying that structs should have identity, there should just be just a way to get notified when a struct gets moved. Maybe a move constructor, something like
1 2 3 4 5 6 7 |
struct Ref(T){ ... this(this,void* oldAddr){ myGC.removeRoot(oldAddr); myGC.addRoot(&m_Ref); } } |
This feature coupeled with the discussed -nogc flag that would replace every reference in the sourcecode with a Ref template inside _object.d would actually allow implementing GCs for the D language that are not based on the mark & sweep approach, because they could track references. (There needs to be a different solution for references inside classes and structures of course)
4. Implicit constructors
A feature I actually like sometimes about C++ is implicit constructors. They often cause a lot of problems but when used correctly they can be very usefull. Again given the “lets implement a different GC” task:
1 2 3 4 5 |
void foo(Object bar){ ... } foo(new Object()); |
If you now want to replace all refrences with a template it would look something like this:
1 2 3 |
void foo(Ref!Object bar){ ... } |
As new does return a Object and not a Ref!Object this will not compile. If there would be a way to define implicit constructors
this problem could be solved:
1 2 3 4 5 |
struct Ref(T){ implicit this(T pRef){ .. } } |
This would introduce a new keyword to the D programming language doing the exact opposide of C++ explicit. You have to delcare implicit constructors not explicit ones. This would be the same “I know what I am doing” approach D usually uses.
5. Shared
Maybe I’m not getting this keyword, but I consider shared not beeing usable. I would highly appreciate a offical document about how shared sould be used, and not only in some small mini example using message passing, but in a realworld performance critical example.
Say for example I have a Stack container:
1 2 3 4 5 6 |
class Stack(T){ ... void push(T el){ ... } T pop(){ ... } bool empty(){ ... } } |
And now I need a threadsafe version of it. Should be quite easy, especially because D supports synchronized classes:
1 2 3 4 5 6 |
synchronized class ThreadsafeStack(T){ Stack!T m_Stack; void push(T el){ m_Stack.push(el); } T pop() { return m_Stack.pop(); } bool empty(){ return m_Stack.empty(); } } |
This will not compile! Because synchronized classes are implicitly shared and shared is transitive, m_Stack will be shared and thus calling the methods of Stack!T is not possible because they are not shared. This transitivy basically kills you everywhere. If you have a shared class basically everything in the hirarchy it accesses has to be shared too. The only way out of this is casting shared away and that just looks and feels plain ugly in the code.
1 2 3 4 5 6 7 8 |
synchronized class ThreadsafeStack(T){ Stack!T m_Stack; void push(el T){ Stack!T stack = cast(Stack!T) m_Stack; stack.push(el); } ... } |
Also the future plan that inside shared methods memory barries will be inserted by the compiler automatically gets me concerened. Especially for performance cirtical applications this will be a performance killer and ist not even needed in most cases. (At least in my experience). As it currently stands I just try to avoid shared completely.
Here is another example. I wanted to create a shared inteface to provide cross thread debug drawing for my application:
1 2 3 |
shared interface IDebugDraw { void DrawBox(vec3 min, vec3 max); } |
The renderer already implemented a non threadsafe version of this method:
1 2 3 |
class Renderer { void DrawBox(vec3 min, vec3 max){ ... } } |
So I just wanted to implement the interface by doing some synchronization and then calling the non threadsafe version:
1 2 3 4 5 6 7 8 9 |
class Renderer : IDebugDraw { void DrawBox(vec3 min, vec3 max) { ... } void DrawBox(vec3 min, vec3 max) shared { synchronized(m_DebugDrawMutex){ Renderer self = cast(Renderer)this; self.DrawBox(min,max); } } } |
This will not compile because you cant overload using shared. Choosing a different name for the function just to differ between the threadsafe and non threadsafe version seems strange to me especially when you have a feature in the type system that should exactly do that.
Another problem is that you are not able to define shared delegates. The type the compiler puts out can actually not be used in sourcecode.
1 2 3 4 5 6 7 8 |
class Foo { void bar() shared {} } void main(string[] args){ Foo foo; static assert(0,typeof(&foo.bar).stringof); } |
If you do this the compiler will put out: “void delegate() shared” as type for bar.
If you now try to use this:
1 2 3 4 |
void main(string[] args){ Foo foo; void delegate() shared del = &foo.bar; } |
It will not compile. Also tried all kinds of variations:
1 2 3 |
(void delegate() shared) del = &foo.bar; shared(void delegate()) del = &foo.bar; void shared delegate() del = &foo.bar; |
None of them will work.
6.Thread Local Storage
There has been a particulary ugly bug in one of my projects connected to TLS. In total TLS was one of the most frequent causes for bugs. Most likely because I was not used to it, but still I want to mention this here:
1 2 3 4 5 6 7 8 9 10 11 |
class Foo { static Manager m_Manager; //unamanged class will not be scanned by GC, there is 1 manager for each thread this(){ m_Manager.register(this); } ~this(){ m_Manager.unregister(this); } } |
As you don’t know in which thread the destructor will be executed as the GC may run in any thread, you can not tell if the class wil actually unregister itself on the same Manager it registered itself. The next thing I tried is creating a lambda inside the destructor and send it to the correct thread to perform the deregistering, unfortunatley this is not possible because no allocations can happen inside the constructor because the GC is currently collecting.
The same problem was also a major issue when freeing OpenGL resources. As the resources have to be freed in the thread that holds the OpenGL context it would crash everytime the GC ran in a different thread than that one. I think there should be at least a warning by the compiler if TLS variables are used inside a destructor because you never know if the TLS variables actually contain what you expect.
7. Associative array invariance
This was actually the second most frequent cause for bugs in my projects. Especially because you don’t get any warnings or asserts in the debug build about it. It just goes into undefined behaviour. If possible the associative array should track all its ranges and see if one of them is still iterating. If so there should be a exception or an assert when trying to change the associative array.
8. std.concurreny TID does not support shared
This is really a pitty. The message passing mechanic prised in the TDPL book does not support shared TID types. Usually you would assume that you can share the TID structure and work on it as the message passing should be threadsafe. Casting away the shared everytime is a pain.
8. No function overloading with template parameters
This one got me especially disappointed after reading Andreis “Modern C++ Design” Book. In there he describes a trick in C++ to overload Functions with template parameters which looks in D basically like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
struct Type2Type(T){ } class Foo1 { void funcHelper(Type2Type!int p){ writefln("a"); } } class Foo2 : Foo1 { alias Foo1.funcHelper funcHelper; void funcHelper(Type2Type!float p){ writefln("b"); } void func(T)(){ funcHelper(Type2Type!(T)()); } } void main(string[] args){ Foo2 foo; foo.func!float(); foo.func!int(); } |
Now I thought that in D it would be possible to do this without this trick as you are able to restrict tempaltes:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Foo1 { void func(T)() if(is(T == int)){ writefln("a"); } } class Foo2 : Foo1 { alias Foo1.func func; void func(T)() if(is(T == float)){ writefln("b"); } } |
Unfortunately this will not compile:
1 |
Error: alias test.Foo2.func conflicts with template test.Foo2.func(T) if (is(T == float)) |
Other then that the biggest problem with the D programming language for me is that the tools are not really major yet. But hopefully this will improve over time.