Type System

Zith is statically typed with a rich set of built-in types and user-defined type constructors.

Primitive Types

Category Types
Unsigned integers u8, u16, u32, u64, u128
Signed integers i8, i16, i32, i64, i128
Floats f32, f64
Other primitives bool, char, void
Compiler-internal unknown (unresolved), invalid (dead/uninitialised)
Special noreturn, null, File, Folder
Opaque opaque (tagged void*), unique opaque (owned), raw opaque (untagged, C interop)

Slice & Array Types

Syntax Meaning
[]T Slice — fat pointer (pointer + length). String literals are []char.
[N]T Fixed-size array, stack-allocated.
[_]T Deduced-size array — compiler infers N from the initialiser.

Strings & Origin Tracking

NRA tracks the origin of every string node (literal, allocator, stack):

// []char implicitly casts to string — zero cost (literal origin)
let s: string = "hello";

// Concatenation changes origin to allocator — triggers allocation
let greeting = "hello" + " " + "world";

ℹ Note: string is a built-in library type. []char is the primitive representation.

Enum

A closed set of named constants. All values must be known at compile time. Three styles:

C-style

enum Direction { North, South, East, West }
enum Status: i32 { Ok = 0, Err = 1, Pending = 2 }

Struct-backed

enum Color: rgb {
    red   = { r: 255, g: 50,  b: 0,   a: 255 },
    green = { 0, 255, 0, 255 },
}

ADT-style (Rust-like)

union Shape {
    Circle = { radius: f32 },
    Rect   = { w: f32, h: f32 },
    Point,
}

enum Constants: union {
    pi      = 3.14f,
    vector  = |x: -1, y: 0, z: -1, w: 1|,
    nothing = 0,
}

fn area(s: Shape): f32 {
    when (s) {
        Circle = s.radius * s.radius * Constants.pi,
        Rect   = s.w * s.h,
        Point  = Constants.nothing,
    }
}

Struct

Field Declaration & Grouping

Individual fields for unrelated fields; [] groups for semantically related fields of the same type:

struct Sample { name: string, age: i32 }

struct Point { [x, y, z]: f32 }

struct Transform {
    [x, y, z]:    f32,     // position
    [rx, ry, rz]: f32,     // rotation
}

Generic Structs

struct Pair<T, U> { first: T, second: U }

Implementation Blocks

implement Pair<T, U> {
    fn swap(self: lend) {
        let tmp = self.first;
        self.first = self.second;
        self.second = tmp;
    }
}

Component

A plain-old-data (POD) struct. Copy by default (alongside primitives). C-compatible layout.

component Vec2 {
    [x, y]: f32,
    fn length(self): f32 { sqrt(self.x * self.x + self.y * self.y) }
    fn dot(self, other: Self): f32 { self.x * other.x + self.y * other.y }
}

Components cannot implement traits, cannot contain self-referential fields, and must be pure data with only inline pure-transformation functions.

Union

By default a union is runtime tagged. Variants separated by commas.

union Value { i32, f64, bool }

let x: union = when (flag) { 0 = 42, 1 = 3.14, 2 = true };

raw union is a C-union, only valid inside raw contexts.

Packs (| |)

A pack unifies variadics, named tuples, and explicit closure captures.

// Bare pack — named tuple
let pos  = |x: 1.0, y: 2.0, z: 0.0|;
@println(pos.x);       // named access
@println(pos.0);       // positional access

// Closure = pack + () + body
let addBase = |view base| (n: i32): i32 { base + n };

// Explicit capture
let f = |base| (n: i32): i32 { base + n };

Union Narrowing (is) & Flow Typing

The is operator narrows a union within a branch. After the block, the type widens back to the full union.

fn handle(v: Val): void {
    if (v is i32) {
        @println("int: {v}");         // v is i32 here
    } else (v is f64) {
        @println("float: {v}");
    } else {
        @println("str: {v}");         // compiler knows v is []char here
    }
    // v is Val again (full union)
}

Generics

// Explicit
fn serialize<T: Serializable + Printable, U: Clone>(val: T, ctx: U): string { ... }

// Implicit — constraints inferred from usage
fn add(a, b) { a + b }

when — Pattern Matching

when (count) {
    0       = @println("none"),
    1       = @println("one"),
    2..10   = @println("few"),
    _       = @println("many"),
}

// As expression
let label = when (score) { 90..100 = "A", 70..90 = "B", _ = "C" };

Cast Operator

let n: i32 = 42;
let f = n as f64;

See also: Spec: Type System


Next: Mutability & Bindings