git

My personal website source code
Log | Files | Refs | Submodules | README | LICENSE

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.