zet-1-classes-betterc-d.md (3762B)
1 --- 2 title: 'Zettelkasten #1: Classes in D with betterC' 3 date: '2021-11-15T00:07:00+01:00' 4 tags: ['zettelkasten', 'zet', 'dlang', 'betterc', 'classes'] 5 description: "This post describes the problem with not having D classes in 6 betterC and both present tricky and reasonable solutions on how to use classes 7 with C++ linkage in D with betterC." 8 --- 9 10 Did you know that you can use classes in D as better C? Yes, you read 11 correctly, you can actually use classes in D with `-betterC`. 12 13 ## Problem involved 14 15 The main problem with having normal classes in D as better C is the dependency 16 of runtime hooks and runtime type information from the D runtime library. Since 17 betterC has some limitations, including forbidden usage of runtime type 18 information, users are usually stuck with features from D that doesn't use the 19 runtime. Although, on the other hand, betterC doesn't limit the usage of C and 20 C++ linkage, since they are not dependent on any runtime library. 21 22 ## What is the tricky part? 23 24 There are tricky parts involved in instantiating `extern(C++)` classes, 25 however. At the time of this post, with the latest version of the compiler, is 26 not easily possible to fetch the init memory block of a class without: 27 28 1. Rely on the `TypeInfo` (use `typeid(ClassFoo).initializer`) 29 2. Create a dummy `scope` instance of the class and copy the memory to a newly 30 allocated buffer. 31 32 Both these options have drawbacks and the first one is only possible on 33 `betterC` if we manually create and remove some symbols. 34 35 The second option is also limitative since the user is not able to use the 36 normal class destructor. If you don't use any special destructor, you can 37 easily fetch that class initializer with something like this: 38 39 ```d 40 static auto initializer() 41 { 42 alias T = typeof(this); 43 void[__traits(classInstanceSize, T)] t = void; 44 scope T s = new T; 45 t[] = (cast(void*)s)[0 .. t.length]; 46 return t; 47 } 48 ``` 49 50 You can also circumvent this issue by using a custom destructor, although you 51 won't benefit from the 52 [RAII](https://ipfs.io/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Resource_Acquisition_Is_Initialization.html) 53 idiom. 54 55 ## A reasonable solution 56 57 Fortunately, LDC has a compiler trait called `__traits(initSymbol)`, which will 58 be soon described by the D specification and 59 [implemented](https://github.com/dlang/dmd/pull/13298) by the reference 60 compiler (DMD) that can do exactly that, fetch the initializer of a class type. 61 62 You just need to create custom allocate and destroy function templates: 63 64 ```d 65 T alloc(T, Args...)(auto ref Args args) 66 { 67 enum tsize = __traits(classInstanceSize, T); 68 T t = () @trusted { 69 import core.memory : pureMalloc; 70 auto _t = cast(T)pureMalloc(tsize); 71 if (!_t) return null; 72 import core.stdc.string : memcpy; 73 memcpy(cast(void*)_t, __traits(initSymbol, T).ptr, tsize); 74 return _t; 75 } (); 76 if(!t) return null; 77 import core.lifetime : forward; 78 t.__ctor(forward!args); 79 80 return t; 81 } 82 83 void destroy(T)(ref T t) 84 { 85 static if (__traits(hasMember, T, "__xdtor")) 86 t.__xdtor(); 87 import core.memory : pureFree; 88 pureFree(cast(void*)t); 89 static if (__traits(compiles, { t = null; })) 90 t = null; 91 } 92 ``` 93 94 With those two functions you can now just allocate a new class instance: 95 96 ```d 97 extern(C++) class Foo 98 { 99 this(int a, float b) 100 { 101 this.a = a * 2; 102 this.b = b; 103 } 104 105 int a; 106 float b; 107 bool c = true; 108 } 109 110 extern(C) int main() 111 { 112 Foo foo = alloc!Foo(2, 2.0f); 113 scope(exit) destroy(foo); 114 115 int a = foo.a; // 4 116 float b = foo.b; // 2.0 117 bool c = foo.c; // true 118 119 return 0; 120 } 121 ``` 122 123 Please note that I haven't covered every corner case about classes with this 124 example.