Skip to content

Type system

CategoryMembers
Integersi8 i16 i32 i64 u8 u16 u32 u64 usize
Floatsf32 f64
Otherboolean string void
CompositeT[] (owned array), [T] (slice), (T, U, ...) (tuple)
Function(T1, T2) => R

usize is target-aware: 4 bytes on wasm32, 8 on arm64 and x86-64. Use it for “size of a thing in memory” semantics.

There aren’t any between numeric widths. Casts are explicit:

var a: i32 = 7;
var b: i64 = a as i64;

The one exception is the polymorphic numeric literal: 1 types as whatever integer the context demands.

Functions and structs/enums take type parameters in [...]:

function id[T](x: T): T {
return x;
}
struct Pair[A, B] { first: A, second: B }
enum Option[T] { Some(T), None }

Generic calls infer T from the argument types when possible. The compiler monomorphises every distinct instantiation before codegen, so there’s no runtime cost.

A union is a closed sum over struct types:

struct Add { l: i32, r: i32 }
struct Mul { l: i32, r: i32 }
type Expr = Add | Mul;
function eval(e: Expr): i32 {
match (e) {
Add(a) => { return a.l + a.r; },
Mul(m) => { return m.l * m.r; },
}
}

The checker desugars unions to synthetic enums with one variant per member; everything downstream (IR / codegen) treats them as ordinary enums.

enum Option[T] { Some(T), None }
enum Result[T, E] { Ok(T), Err(E) }

Both are injected by the prelude — declare them yourself only to shadow them.

The postfix ? operator unwraps the success variant and early- returns the failure variant:

function parse(s: string): Result[i32, string] {
// ...
}
function double(s: string): Result[i32, string] {
var n: i32 = parse(s)?; // bails on Err
return Ok(n * 2);
}