Djinn Lang

Type System

Built-in Types

Check types details

Type

Description

i(N)Integer's type
u(N)Unsigned integer's type
boolBoolean type
arr<T>Array slice
strString slice
T*Pointer type

Defined Types

You can define your own types using keyword struct

struct User {
    string name;
    i32 age;
    // ...
}

All fields can be described with modifiers, then type and finally their name

Generic Types

A generic struct can have any number of generic types, they must be compile-time known. When compiled, it creates each type its own morphilized struct

struct Repository<TEntity, TKey> {
    DatabaseConnection connection;

    public TEntity getById(TKey id) { 
        //.. do work 
    }
}

Transparent Types

A transparent type wraps a primitive with its own type identity. Each transparent type is unique at compile time — c_result is not the same as i32, even though the generated code is identical. This gives you stronger type checking (e.g. when binding C libraries) without any runtime cost.

struct c_result : i32;
struct bool : i1;

You can also implement methods on transparent types, just like regular structs. The compiler lowers them to the underlying primitive in the generated code, so there is zero overhead.

Object Boxing

The std::types::object type represents a boxed value that carries both the data and its type information at runtime.

struct TypeInfo {
    i32 id;
    i32 size;
    i8* name;
    u8 kind;
}

struct object : Hashable {
    TypeInfo* type;
    void* data;
}

TypeInfo stores the type's numeric id, byte size, name string, and a kind discriminator. The object struct holds a pointer to its TypeInfo and a void* to the actual data.

When is boxing used?

Boxing happens automatically in two cases:

  • Variadic functions — all arguments passed to variadic parameters are boxed, so you can inspect their type at runtime
  • object variables or parameters — assigning any value to an object variable or passing it to an object parameter triggers automatic boxing
i32 foo(object obj) {
    printf("object kind: %d\n", obj.type.kind);
    printf("hash: %d\n", obj.type.hash());
    printf("name: %s\n", obj.type.name);
}

void main() {
    object bar = "hello world";
    foo(bar);
}

Output:

object kind: 3
hash: 983072202
name: std::types::object

Through the TypeInfo pointer you can query the kind, name, and hash of any boxed value at runtime.

Type Kind

The kind field allows the runtime to read and format values without unboxing them. The kind values are:

KindType
0Integer (i8..i64, u8..u64)
1Float (f32, f64)
2Pointer (i8*, strings)
3Struct

Combined with type.size, the runtime can cast void* data to the correct pointer type and read the value directly — no unboxing needed.

void inspect(object obj) {
    if (obj.type.kind == 0 && obj.type.size <= 4) {
        i32* val = (i32*)obj.data;
        printf("integer: %d\n", *val);
    } else if (obj.type.kind == 1) {
        f64* val = (f64*)obj.data;
        printf("float: %g\n", *val);
    } else if (obj.type.kind == 2) {
        i8* val = (i8*)obj.data;
        printf("string: %s\n", val);
    }
}

On this page