3.18. Distinct Types

A distinct type promotes a plain “weak” type (int, float, void?, or any other workhorse type) to a unique nominal type. It has the exact ABI of its underlying type — same size, alignment, and pass-by-value semantics — but it does not interconvert with it: functions taking int won’t take a distinct type over int, and vice versa. This makes handles, IDs, and unit-like values type-safe at zero runtime cost.

typedef distinct EntityId = int
typedef distinct Meters = float
typedef distinct FileHandle = void?

Distinct types are declared at module scope with typedef distinct — the typedef family form that creates a nominal type instead of a transparent alias (distinct itself is not a keyword, so identifiers named distinct — like the linq function — keep working). The underlying type must be a workhorse type (a value type that passes by value — int/uint and friends, float, double, bool, string, pointers, vector types like float3, ranges). A distinct type cannot be defined in terms of another distinct type.

3.18.1. Construction and Deref

The only way in is explicit construction from a value of exactly the underlying type — a pure compile-time relabel with no runtime cost:

var id = EntityId(5)
var m : Meters = Meters(2.5)
// var bad : EntityId = 5      // error: no implicit conversion

The only way out is the dereference operator *, which peels exactly one distinct level and yields the underlying type as a reference. Constness flows from the handle: dereferencing a var gives writable access, a let gives const access.

var id = EntityId(5)
print("{*id}\n")        // output: 5
*id = 7                 // writes through - *id is int&
if (*id == 7) {
    print("seven\n")    // output: seven
}

For a distinct type over a pointer, * peels the distinct level, not the pointer: *fileHandle yields void?.

var id : EntityId zero-initializes, and default<EntityId> is the zero value.

3.18.2. Operators

Only == and != are borrowed from the underlying type, and both sides must be the same distinct type. Everything else — arithmetic, ordering — is deliberately absent: EntityId + EntityId is meaningless, so it doesn’t compile. Where an operation is meaningful, define it explicitly:

def operator + (a, b : Meters) : Meters => Meters(*a + *b)

let d = Meters(2.0) + Meters(3.0)   // Meters(5.0)

Distinct types cannot be table keys (even though the underlying type hashes fine — the nominal wall is the point). Arrays, struct fields, tuple and variant fields all work.

3.18.3. Overloads

Because the mangled names differ, overloads on the distinct type and its underlying type coexist — this is the primary use case:

def describe(x : int) : string => "int {x}"
def describe(x : EntityId) : string => "entity {*x}"

3.18.4. Modules and Privacy

Distinct types are module-nominal: same-named distinct types in two modules are different types. typedef private distinct Foo = int limits visibility to the declaring module, like enum private.

3.18.5. Runtime Erasure

Distinct types exist only in the type system. At runtime — in the interpreter, JIT, AOT, and reflection — values carry their underlying type: print(id) prints 7, not EntityId(7), and RTTI reports the underlying type. The compile-time relabel is free in all three execution tiers.

3.18.6. C++ Integration

A C++ module can register a distinct type with DistinctTypeAnnotation and expose extern functions whose signatures carry it:

// struct NativeId { int32_t value; ... };  with das::cast/WrapType specializations
MAKE_DISTINCT_TYPE_FACTORY(NativeId, NativeId);
// in the module constructor:
addAnnotation(new DistinctTypeAnnotation("NativeId", makeType<int32_t>(lib), "NativeId"));
addExtern<DAS_BIND_FUN(nativeIdNext)>(*this, lib, "native_id_next", SideEffects::none, "nativeIdNext");

On the das side NativeId behaves exactly like a das-declared distinct type: constructed with NativeId(v), dereferenced with *, and never interchangeable with int.

See also

Type Aliases for transparent (non-nominal) type names, Datatypes for the workhorse types that can be made distinct, Constants and Enumerations for the other nominal value types.