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