commit 9e4165548dbdd5d72b281cc5d773e54edef0c63d
parent 79a58faf7c54eca5fabf4f2e930b046301862c07
Author: Luís Ferreira <contact@lsferreira.net>
Date: Mon, 15 Nov 2021 01:48:56 +0000
posts: add my first zettelkasten
Signed-off-by: Luís Ferreira <contact@lsferreira.net>
Diffstat:
1 file changed, 119 insertions(+), 0 deletions(-)
diff --git a/content/posts/zet-1-classes-betterc-d.md b/content/posts/zet-1-classes-betterc-d.md
@@ -0,0 +1,119 @@
+---
+title: 'Zettelkasten #1: Classes in D with betterC'
+date: '2021-11-15T00:07:00+01:00'
+tags: ['zettelkasten', 'zet', 'dlang', 'betterc', 'classes']
+description: "This post describes the problem with not having D classes in
+betterC and both present tricky and reasonable solutions on how to use classes
+with C++ linkage in D with betterC."
+---
+
+Did you know that you can use classes in D as better C? Yes, you read
+correctly, you can actually use classes in D with `-betterC`.
+
+## Problem involved
+
+The main problem with having normal classes in D as better C is the dependency
+of runtime hooks and runtime type information from the D runtime library. Since
+betterC has some limitations, including forbidden usage of runtime type
+information, users are usually stuck with features from D that doesn't use the
+runtime. Although, on the other hand, betterC doesn't limit the usage of C and
+C++ linkage, since they are not dependent on any runtime library.
+
+## What is the tricky part?
+
+There are tricky parts involved in instantiating `extern(C++)` classes,
+however. At the time of this post, with the latest version of the compiler, is
+not easily possible to fetch the init memory block of a class without:
+
+1. Rely on the `TypeInfo` (use `typeid(ClassFoo).initializer`)
+2. Create a dummy `scope` instance of the class and copy the memory to a newly
+ allocated buffer.
+
+Both these options have drawbacks and the first one is only possible on
+`betterC` if we manually create and remove some symbols.
+
+The second option is also limitative since the user is not able to use the
+normal class destructor. If you don't use any special destructor, you can
+easily fetch that class initializer with something like this:
+
+```d
+static auto initializer()
+{
+ alias T = typeof(this);
+ void[__traits(classInstanceSize, T)] t = void;
+ scope T s = new T;
+ t[] = (cast(void*)s)[0 .. t.length];
+ return t;
+}
+```
+
+You can also circumvent this issue by using a custom destructor, although you
+won't benefit from the
+[RAII](https://ipfs.io/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Resource_Acquisition_Is_Initialization.html)
+idiom.
+
+## A reasonable solution
+
+Fortunately, LDC has a compiler trait called `__traits(initSymbol)`, which will
+be soon described by the D specification and
+[implemented](https://github.com/dlang/dmd/pull/13298) by the reference
+compiler (DMD) that can do exactly that, fetch the initializer of a class type.
+
+You just need to create custom allocate and destroy function templates:
+
+```d
+T alloc(T, Args...)(auto ref Args args)
+{
+ enum tsize = __traits(classInstanceSize, T);
+ T t = () @trusted {
+ import core.memory : pureMalloc;
+ auto _t = cast(T)pureMalloc(tsize);
+ if (!_t) return null;
+ import core.stdc.string : memcpy;
+ memcpy(cast(void*)_t, __traits(initSymbol, T).ptr, tsize);
+ return _t;
+ } ();
+ if(!t) return null;
+ t.__ctor(args);
+
+ return t;
+}
+
+void destroy(T)(ref T t)
+{
+ t.__dtor();
+ () @trusted {
+ import core.memory : pureFree;
+ pureFree(cast(void*)t);
+ }();
+ t = null;
+}
+```
+
+With those two functions you can now just allocate a new class instance:
+
+```d
+extern(C++) class Foo
+{
+ this(int a, float b)
+ {
+ this.a = a * 2;
+ this.b = b;
+ }
+
+ int a;
+ float b;
+ bool c = true;
+}
+
+extern(C) int main()
+{
+ Foo foo = alloc!Foo(2, 2.0f);
+
+ int a = foo.a; // 4
+ float b = foo.b; // 2.0
+ bool c = foo.c; // true
+
+ return 0;
+}
+```