Storing interfaces as void[]

tsbockman thomas.bockman at gmail.com
Sat Mar 13 03:52:52 UTC 2021


On Saturday, 13 March 2021 at 00:36:37 UTC, David  Zhang wrote:
> On Friday, 12 March 2021 at 22:18:59 UTC, tsbockman wrote:
>> You can use TypeInfo references as the keys for the struct 
>> types, too. It's better than hashes because the TypeInfo is 
>> already being generated anyway, and is guaranteed to be 
>> unique, unlike your hashes which could theoretically collide.
>
> Makes sense. Using TypeInfo never occurred to me. I assume they 
> are generated for COM classes as well?

I'm not sure about that; you should test it yourself. I know that 
runtime type information support is incomplete for some 
non-`extern(D)` types - for example:
     https://issues.dlang.org/show_bug.cgi?id=21690
You can always fall back to fully qualified names for 
non-`extern(D)` stuff if you have to.

But you should protect against hash collisions somehow, if you go 
that route. Here's a hybrid approach that is immune to hash 
collisions and works across DLL boundaries, but can almost always 
verify equality with a single pointer comparison:

///////////////////////////////////////
struct TypeKey {
private:
     const(void)* ptr;
     size_t length;

     this(const(TypeInfo) typeInfo) const pure @trusted nothrow 
@nogc {
         ptr = cast(const(void)*) typeInfo;
         length = 0u;
     }
     this(string fqn) immutable pure @trusted nothrow {
         /* We need to allocate a block of size_t to ensure proper 
alignment
         for the first chunk, which is the hash code of the fqn 
string: */
         size_t[] chunks = new size_t[1 + (fqn.length + 
(size_t.sizeof - 1)) / size_t.sizeof];
         chunks[0] = hashOf(fqn);
         (cast(char*) chunks.ptr)[size_t.sizeof .. size_t.sizeof + 
fqn.length] = fqn;

         ptr = cast(immutable(void)*) chunks.ptr;
         length = fqn.length;
     }

     @property const(TypeInfo) typeInfo() const pure @trusted 
nothrow @nogc
         in(length == 0u)
     {
         return cast(const(TypeInfo)) ptr;
     }
     @property size_t fqnHash() const pure @trusted nothrow @nogc
         in(length != 0u)
     {
         return *cast(const(size_t)*) ptr;
     }
     @property string fqn() const pure @trusted nothrow @nogc
         in(length != 0u)
     {
         const fqnPtr = cast(immutable(char)*) (this.ptr + 
size_t.sizeof);
         return fqnPtr[0 .. length];
     }

public:
     string toString() const @safe {
         return (length == 0u)? typeInfo.toString() : fqn; }
     size_t toHash() const @safe nothrow {
         return (length == 0u)? typeInfo.toHash : fqnHash; }
     bool opEquals(TypeKey that) const @trusted {
         if(this.ptr is that.ptr)
             return true;
         if(this.length != that.length)
             return false;
         if(length == 0u)
             return (this.typeInfo == that.typeInfo);
         if(this.fqnHash != that.fqnHash)
             return false;
         return (this.fqn == that.fqn);
     }
}
template typeKeyOf(Indirect)
     if(is(Indirect : T*, T) // Support structs, static arrays, 
etc.
     || is(Indirect == interface) || is(Indirect == class))
{
     static if(is(Indirect : T*, T) || (__traits(getLinkage, 
Indirect) == "D")) {
         @property const(TypeKey) typeKeyOf() pure @safe nothrow 
@nogc {
             return const(TypeKey)(typeid(Indirect)); }
     } else {
         /* For FQN-based keys, ideally the whole process should 
share a single
         copy of the heap allocated fqn and its hash, so we'll use 
a global. No
         synchronization is necessary post construction, since it 
is immutable: */
         private immutable TypeKey masterKey;
         shared static this() {
             // With some bit-twiddling, this could be done at 
compile time, if needed:
             import std.traits : fullyQualifiedName;
             masterKey = 
immutable(TypeKey)(fullyQualifiedName!Indirect);
         }
         @property immutable(TypeKey) typeKeyOf() @safe nothrow 
@nogc {
             assert(masterKey.ptr !is null);
             return masterKey;
         }
     }
}
@safe unittest {
     static extern(C++) class X { }
     static extern(C++) class Y { }

     assert(typeKeyOf!(int*) == typeKeyOf!(int*));
     assert(typeKeyOf!(int*) != typeKeyOf!(float*));
     assert(typeKeyOf!X == typeKeyOf!X);
     assert(typeKeyOf!X != typeKeyOf!Y);
     assert(typeKeyOf!X != typeKeyOf!(int*));
     assert(typeKeyOf!(float*) != typeKeyOf!Y);
}
///////////////////////////////////////

>> The lowering is something like this:
>> ...
> Makes sense, I always thought of them as existing in separate 
> places.

Yeah, there are multiple reasonable ways of supporting interfaces 
in a language, each with their own trade-offs. But, this is the 
one used by D at the moment.

> So much head-bashing, and it's over. Thanks, tsbockman, 
> Imperatorn.

You're very welcome!


More information about the Digitalmars-d-learn mailing list