Second draft: Sum Type by Struct
Per Nordlöw
per.nordlow at gmail.com
Thu Sep 10 16:54:51 UTC 2026
On Thursday, 3 September 2026 at 11:45:12 UTC, Richard (Rikki)
Andrew Cattermole wrote:
> This is the second iteration of sumtype by struct proposal.
Can we please reserve the support for sub-byte packing of `N` tag
bits for the sake of compacter memory layouts such as polymorphic
pointers being only of `2^^N` different types? I've been using
this for some time for instance in my
```d
import nxt.attribute;
import nxt.features;
/++ A GC scanning-compliant variant of `Types` packed into a word
`size_t`).
*
* Requirements for `Types`:
* - Pointer types: Automatically compatible (naturally aligned)
* - Value types: Must reserve lower `tagBits` in a field named
`_tag`.
*
* Example value type:
* ---
* struct MyValue {
* version(LittleEndian) {
* private ulong _tag: 2; // reserved for `GCWordVariant`
* ulong data: 62;
* } else {
* ulong data: 62;
* private ulong _tag: 2;
* }
* }
* ---
+/
struct GCWordVariant(Types...) if (Types.length >= 1) {
pragma(inline, true):
import nxt.meta : staticIndexOf;
static if (Types.length == 1)
pragma(msg, __FILE__, "(", __LINE__, ",1): Warning: Instead use
`Option!(", T, ")`");
static foreach (T; Types) {
// TODO: If `T` is a pointer do this otherwise check calculate
the number of bits needed in enum's etc.
static assert(T.sizeof <= size_t.sizeof, "(", T, ").sizeof
being ", T.sizeof, " must be smaller than ", size_t.sizeof);
}
private:
enum N = Types.length + 1; /// One extra state for uninitialized.
/++ Number of bits needed to encode union type tag. +/
static if (N <= 2) enum tagBits = 1;
else static if (N <= 4) enum tagBits = 2;
else static if (N <= 8) enum tagBits = 3;
else static assert("Types.length being ", Types.length, " must
be <= 7 in order to fit into the 3 least significant bits of
`this` to guarantee that this is scannable by GC");
enum tagMask = (1UL << tagBits) - 1;
public:
this(T)(T value) @trusted if (staticIndexOf!(T, Types) >= 0) {
static if (!is(T == U*, U)) {
enum tagName = "_tag"; /// Name of tag field.
static assert(__traits(hasMember, T, tagName),
"Non-pointer type " ~ T.stringof ~ " is missing a `",
tagName, "` bitfield to reserve lower " ~ tagBits.stringof ~ "
bits for discriminator type tag");
static if (__traits(isBitfield, __traits(getMember, T,
tagName))) { // `T` has bitfield named `tagName`
enum W = __traits(getBitfieldWidth, __traits(getMember, T,
tagName));
static assert(W >= tagBits, "The bit-size of bitfield `",
tagName, "` in ", T.stringof, " being ", W.stringof, " < ",
tagBits, " bits");
} else { // `T` has normal field named `tagName`
enum W = 8 * __traits(getMember, T, tagName).sizeof;
static assert(W >= tagBits,
"The bit-size of field `", tagName, "` in ", T.stringof, "
being ", W.stringof, " < ", tagBits, " bits");
}
}
const raw = (*(cast(size_t*)(&value)));
assert(!(raw & tagMask), `Some tag bits are non-zero`);
_store = (raw | // data in lower part
(cast(size_t)(staticIndexOf!(T, Types) + 1))); // use lower
bits for type information
}
@property:
inout(T) get(T)() inout @trusted if (staticIndexOf!(T, Types) >=
0) in(isA!T) {
static if (is(T == U*, U)) {
return cast(inout(T))(_store & ~tagMask);
} else {
const val = _value; // TODO: avoid this intermediate if
possible
return *(cast(typeof(return)*)(&val)); // reinterpret cast
}
}
bool opCast(T : bool)() const scope => _store != _store.init;
bool isA(T)() const scope => _tag == staticIndexOf!(T, Types) +
1;
package(nxt):
/++ Get type tag of currently stored value.
Returns:
- 0: null (uninitialized)
- `n` >= 1: 1-based offset `n` into `Types`.
+/
auto _tag() const scope => ((_store & tagMask));
size_t _value() const scope => _store & ~tagMask;
size_t _store; // raw untyped word
}
pure nothrow version(nxt_test) unittest {
enum tagBits = 2;
struct Leaf {
version(LittleEndian) {
private ulong _tag: tagBits; // reserved for `GCWordVariant `
ulong value: (8 * long.sizeof) - tagBits;
} else {
ulong value: (8 * long.sizeof) - tagBits;
private ulong _tag: tagBits; // reserved for `GCWordVariant `
}
}
struct Array {}
struct Object_ {}
alias T = V!(Leaf, Array*, Object_*);
static assert(T.tagBits == tagBits);
assert(!T());
assert(T()._tag == 0);
auto leaf = Leaf(value: 42);
auto t_leaf = T(leaf);
assert(t_leaf);
assert(t_leaf._tag == 1);
assert(t_leaf.get!Leaf == leaf);
auto t_array = T((Array*).init);
assert(t_array);
assert(t_array._tag == 2);
auto t_object = T((Object_*).init);
assert(t_object);
assert(t_object._tag == 3);
}
version(nxt_test) version(unittest) {
import nxt.internal.traits : Seq;
static private:
alias V = GCWordVariant;
}
```
.
More information about the dip.development
mailing list